aboutsummaryrefslogtreecommitdiff
path: root/files/es/web/progressive_web_apps/loading/index.html
blob: bc44773494abfb380755319e17700f0bae76d356 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
---
title: Carga progresiva
slug: Web/Progressive_web_apps/Loading
tags:
  - Cargar
  - PWAs
  - aplicaciones web progresivas
  - js13kGames
  - progresiva
translation_of: Web/Progressive_web_apps/Loading
---
<div>{{PreviousMenu("Web/Progressive_web_apps/Re-engageable_Notifications_Push", "Web/Progressive_web_apps")}}</div>

<p class="summary">En artículos anteriores cubrimos las API que nos ayudan a convertir nuestro ejemplo de <a href="https://mdn.github.io/pwa-examples/js13kpwa/">js13kPWA</a> en una aplicación web progresiva: {{web.link("/es/docs/Web/Progressive_web_apps/Offline_Service_workers", "El servicio workers")}}, {{web.link("/es/docs/Web/Progressive_web_apps/Installable_PWAs", "Manifiestos web")}}, {{web.link("/es/docs/Web/Progressive_web_apps/Re-engagementable_Notifications_Push", "Notificaciones y Push")}}. En este artículo iremos aún más allá y mejoraremos el rendimiento de la aplicación cargando progresivamente sus recursos.</p>

<h2 id="Primera_imagen_significativa">Primera imagen significativa</h2>

<p>Es importante entregar algo significativo al usuario lo antes posible: cuanto más espere a que se cargue la página, mayor será la posibilidad de que se vaya antes de esperar a que todo termine. Deberíamos poder mostrarles al menos la vista básica de la página que quieren ver, con marcadores de posición en los lugares donde eventualmente se cargará más contenido.</p>

<p>Esto se podría lograr mediante la carga progresiva, también conocida como <a href="https://en.wikipedia.org/wiki/Lazy_loading">carga diferida</a>. Se trata de aplazar la carga de recursos tanto como sea posible (HTML, CSS, JavaScript) y solo cargar inmediatamente los que realmente se necesitan para la primera experiencia.</p>

<h2 id="Agrupar_versus_dividir">Agrupar versus dividir</h2>

<p>Muchos visitantes no pasarán por todas las páginas de un sitio web, sin embargo, el enfoque habitual es agrupar todas las funciones que tenemos en un archivo grande. Un archivo <code>bundle.js</code> puede tener muchos megabytes, y un solo paquete <code>style.css</code> puede contener de todo, desde definiciones básicas de estructura CSS hasta todos los estilos posibles de cada versión del sitio: móvil, tableta, escritorio, solo impresión, etc.</p>

<p>Es más rápido cargar toda esa información como un archivo en lugar de muchos archivos pequeños, pero si el usuario no necesita todo al principio, podríamos cargar solo lo que es crucial y luego gestionar otros recursos cuando sea necesario.</p>

<h2 id="Recursos_que_bloquean_el_renderizado">Recursos que bloquean el renderizado</h2>

<p>La agrupación es un problema, porque el navegador tiene que cargar HTML, CSS y JavaScript antes de poder pintar sus resultados renderizados en la pantalla. Durante los pocos segundos entre el acceso inicial al sitio web y la finalización de la carga, el usuario ve una página en blanco, lo cual es una mala experiencia.</p>

<p>Para solucionarlo, podemos, por ejemplo, agregar <code>defer</code> a los archivos JavaScript:</p>

<pre class="brush: html notranslate">&lt;script src="app.js" defer&gt;&lt;/script&gt;
</pre>

<p>Se descargarán y ejecutarán <em>después</em> que el documento en sí haya sido procesado, por lo que no bloqueará la representación de la estructura HTML. También podemos dividir archivos css y agregarles tipos de medios:</p>

<pre class="brush: html notranslate">&lt;link rel="stylesheet" href="style.css"&gt;
&lt;link rel="stylesheet" href="print.css" media="print"&gt;
</pre>

<p>Esto le indicará al navegador que los cargue solo cuando se cumpla la condición.</p>

<p>En nuestra aplicación de demostración js13kPWA, el CSS es lo suficientemente simple como para dejarlo todo en un solo archivo sin reglas específicas sobre cómo cargarlos. Podríamos ir aún más lejos y mover todo desde <code>style.css</code> a la etiqueta <code>&lt;style&gt;</code> en el <code>&lt;head&gt;</code> del <code>index.html</code>: esto mejoraría aún más el rendimiento, pero para la legibilidad del ejemplo también omitiremos este enfoque.</p>

<h2 id="Imágenes">Imágenes</h2>

<p>Además de JavaScript y CSS, es probable que los sitios web contengan varias imágenes. Cuando incluyes elementos {{HTMLElement("img")}} en tu HTML, todas las imágenes a las que se hace referencia se buscarán y descargarán durante el acceso inicial al sitio web. No es inusual tener megabytes de datos de imágenes para descargar antes de anunciar que el sitio está listo, pero esto nuevamente crea una mala percepción del rendimiento. No necesitamos todas las imágenes en la mejor calidad posible al comienzo de la visualización del sitio.</p>

<p>Esto se puede optimizar. En primer lugar, debes utilizar herramientas o servicios similares a <a href="https://tinypng.com/">TinyPNG</a>, que reducirán el tamaño del archivo de tus imágenes sin alterar demasiado la calidad. Si has superado ese punto, puede empezar a pensar en optimizar la carga de imágenes mediante JavaScript. Explicaremos esto a continuación.</p>

<h3 id="Marcador_de_posición_de_imagen">Marcador de posición de imagen</h3>

<p>En lugar de tener todas las capturas de pantalla de los juegos referenciados en los atributos del elemento <code>&lt;img&gt;</code> <code>src</code>, que obligarán al navegador a descargarlos automáticamente, podemos hacerlo de forma selectiva a través de JavaScript. En su lugar, la aplicación js13kPWA usa una imagen de marcador de posición, que es pequeña y liviana, mientras que las rutas finales a las imágenes de destino se almacenan en los atributos <code>data-src</code>:</p>

<pre class="brush: html notranslate">&lt;img src='data/img/placeholder.png' data-src='data/img/SLUG.jpg' alt='NAME'&gt;
</pre>

<p>Esas imágenes se cargarán mediante JavaScript <em>después</em> que el sitio termine de construir la estructura HTML. La imagen del marcador de posición se escala de la misma manera que las imágenes originales, por lo que ocupará el mismo espacio y no hará que el diseño se vuelva a pintar a medida que se cargan las imágenes.</p>

<h3 id="Cargar_a_través_de_JavaScript">Cargar a través de JavaScript</h3>

<p>El archivo <code>app.js</code> procesa los atributos <code>data-src</code> así:</p>

<pre class="brush: js notranslate">let imagesToLoad = document.querySelectorAll('img[data-src]');
const loadImages = (image) =&gt; {
  image.setAttribute('src', image.getAttribute('data-src'));
  image.onload = () =&gt; {
    image.removeAttribute('data-src');
  };
};</pre>

<p>La variable <code>imagesToLoad</code> contiene referencias a todas las imágenes, mientras que la función <code>loadImages</code> mueve la ruta de <code>data-src</code> a <code>src</code>. Cuando cada imagen está realmente cargada, eliminamos su atributo <code>data-src</code> porque ya no es necesario. Luego recorremos cada imagen y la cargamos:</p>

<pre class="brush: js notranslate">imagesToLoad.forEach((img) =&gt; {
  loadImages(img);
});</pre>

<h3 id="Desenfoque_en_CSS">Desenfoque en CSS</h3>

<p>Para que todo el proceso sea más atractivo visualmente, el marcador de posición se difumina en CSS.</p>

<p><img alt="Captura de pantalla de imágenes de marcador de posición en la aplicación js13kPWA." src="https://mdn.mozillademos.org/files/15992/js13kpwa-placeholders.png" style="height: 684px; width: 675px;"></p>

<p>Renderizamos las imágenes con un desenfoque al principio, por lo que se puede lograr una transición hacia la nitidez:</p>

<pre class="brush: css notranslate">article img[data-src] {
  filter: blur(0.2em);
}

article img {
  filter: blur(0em);
  transition: filter 0.5s;
}</pre>

<p>Esto eliminará el efecto de desenfoque en medio segundo, el cual se ve lo suficientemente bien para el efecto de "carga".</p>

<h2 id="Carga_bajo_demanda">Carga bajo demanda</h2>

<p>El mecanismo de carga de imágenes explicado en la sección anterior funciona bien: carga las imágenes después de renderizar la estructura HTML y aplica un agradable efecto de transición en el proceso. El problema es que todavía carga <em>todas</em> las imágenes simultáneamente, aunque el usuario solo verá las dos o tres primeras al cargar la página.</p>

<p>Este problema se puede resolver con la nueva {{web.link("/es/docs/Web/API/Intersection_Observer_API", "API observador de intersecciones")}}; con esto nos podemos asegurar de que las imágenes se carguen solo cuando aparezcan en la ventana gráfica.</p>

<h3 id="Observador_de_intersecciones">Observador de intersecciones</h3>

<p>Esta es una mejora progresiva del ejemplo de uso anterior: {{web.link("/es/docs/Web/API/Intersection_Observer_API", "Observador de intersección")}} cargará las imágenes destino solo cuando el usuario se desplaza hacia abajo, lo cual hace que se exhiban en la ventana gráfica.</p>

<p>Así es como se ve el código relevante:</p>

<pre class="brush: js notranslate">if('IntersectionObserver' in window) {
  const observer = new IntersectionObserver((items, observer) =&gt; {
    items.forEach((item) =&gt; {
      if(item.isIntersecting) {
        loadImages(item.target);
        observer.unobserve(item.target);
      }
    });
  });
  imagesToLoad.forEach((img) =&gt; {
    observer.observe(img);
  });
} else {
  imagesToLoad.forEach((img) =&gt; {
    loadImages(img);
  });
}</pre>

<p>Si se admite el objeto {{DOMxRef("IntersectionObserver")}}, la aplicación crea una nueva instancia del mismo. La función pasada como parámetro está manejando el caso cuando uno o más elementos se cruzan con el observador (es decir, aparecen dentro de la ventana gráfica). Podemos iterar sobre cada caso y reaccionar en consecuencia: cuando una imagen es visible, cargamos la imagen correcta y dejamos de observarla porque ya no necesitamos observarla.</p>

<p>Reiteremos nuestra mención anterior de la mejora progresiva: el código está escrito para que la aplicación funcione tanto si <code>Intersection Observer</code> es compatible como si no. Si no es así, simplemente cargamos las imágenes usando el enfoque más básico cubierto anteriormente.</p>

<h2 id="Mejoras">Mejoras</h2>

<p>Recuerda que hay muchas formas de optimizar los tiempos de carga, y este ejemplo explora solo uno de los enfoques. Puedes intentar hacer que tus aplicaciones sean más a prueba de balas haciéndolas funcionar sin JavaScript, ya sea utilizando {{HTMLElement("noscript")}} para mostrar la imagen con el <code>src</code> final ya asignado, o envolviendo <code>Etiquetas &lt;img&gt;</code> con elementos {{HTMLElement("a")}} que apuntan a las imágenes destino, para que el usuario pueda hacer clic y acceder a ellas cuando lo desee.</p>

<p>No lo haremos porque la aplicación en sí depende de JavaScript; sin él, la lista de juegos ni siquiera se cargaría y el código del servicio <em>worker</em> no se ejecutaría.</p>

<p>Podríamos reescribir el proceso de carga para cargar no solo las imágenes, sino los elementos completos que consisten en descripciones completas y enlaces. Funcionaría como un desplazamiento infinito: cargar los elementos de la lista solo cuando el usuario desplaza la página hacia abajo. De esa manera, la estructura HTML inicial sería mínima, el tiempo de carga sería aún menor y tendríamos beneficios de rendimiento aún mayores.</p>

<h2 id="Conclusión">Conclusión</h2>

<p>Menos archivos para cargar inicialmente, archivos más pequeños divididos en módulos, uso de marcadores de posición y carga de más contenido bajo demanda: esto ayudará a lograr tiempos de carga inicial más rápidos, lo que brinda beneficios al creador de la aplicación y ofrece una experiencia más fluida para el usuario.</p>

<p>Recuerda el enfoque de mejora progresiva: ofrece un producto utilizable sin importar el dispositivo o la plataforma, pero asegúrate de enriquecer la experiencia a quienes utilizan navegadores modernos.</p>

<h2 id="Pensamientos_finales">Pensamientos finales</h2>

<p>Eso es todo por esta serie de tutoriales: revisamos el <a href="https://github.com/mdn/pwa-examples/tree/master/js13kpwa">código fuente de la aplicación de ejemplo js13kPWA</a> y aprendimos sobre el uso de funciones de aplicaciones web progresivas, incluida una {{web.link("/es/docs/Web/Progressive_web_apps/Introduction", "Introducción")}}, {{web.link("/es/docs/Web/Progressive_web_apps/App_structure", "estructura PWA")}}, {{web.link("/es/docs/Web/Progressive_web_apps/Offline_Service_workers", "disponibilidad sin conexión con servicio workers")}}, {{web.link("/es/docs/Web/Progressive_web_apps/Installable_PWAs", "PWAs instalables")}}, y finalmente notificaciones. También explicamos el <code>push</code> con la ayuda del <a href="https://serviceworke.rs/">Libro de recetas para el servicio <em>workers</em></a>. Y en este artículo, hemos analizado el concepto de carga progresiva, incluido un interesante ejemplo que hace uso de la {{web.link("/es/docs/Web/API/Intersection_Observer_API", "API de Intersection Observer")}}.</p>

<p>No dudes en experimentar con el código, mejorar tu aplicación existente con funciones de PWA o crear algo completamente nuevo por tu cuenta. Las PWAs ofrecen una gran ventaja sobre las aplicaciones web habituales.</p>

<p>{{PreviousMenu("Web/Progressive_web_apps/Re-engageable_Notifications_Push", "Web/Progressive_web_apps")}}</p>

<p>{{QuickLinksWithSubpages("/es/docs/Web/Progressive_web_apps/")}}</p>