Escrito por Ramón Saquete
Índice
La manera de incluir imágenes dentro del código HTML ha evolucionado constantemente en los últimos años para cubrir las exigencias de rendimiento y visualización en distintos dispositivos, convirtiéndose en una tarea más compleja. A continuación, vamos a ver qué características son deseables en la carga óptima de una imagen y cómo alcanzarlas con los estándares disponibles actualmente.
Primero vamos a ver una lista de todos los problemas que deberíamos abordar a la hora de cargar una imagen y después veremos cómo se resuelven.
Lista de características deseables de una imagen en una web optimizada
Una tarea aparentemente sencilla como mostrar una imagen puede ser más complicada de lo que puede parecer a priori si queremos hacerlo bien.
Son objetivos difíciles de cumplir y para hacerlo hemos de tener en cuenta los puntos siguientes:
- La imagen se tiene que poder indexar fácilmente por cualquier araña tanto con como sin JavaScript. En el caso de Google, tendremos una primera pasada de indexación sin JavaScript y una posible segunda pasada con JavaScript.
- Debe existir un texto alternativo SEO que se muestre si ocurre un error al cargar la imagen.
- Necesitamos distintos tamaños de la imagen para los distintos tamaños y resoluciones de pantalla, de forma que la imagen se vea correctamente en cualquier dispositivo.
- La compresión de la imagen debe ser óptima. Para ello debemos:
- Seleccionar correctamente el formato de imagen más adecuado y ajustar los parámetros de guardado al tipo de imagen.
- Tener disponible una versión optimizada en el formato WebP para cada tamaño de imagen. Este formato está soportado actualmente en todos los navegadores excepto en Safari, donde el soporte actualmente es parcial.
- Para Safari podríamos tener disponible una versión optimizada en JPEG 2000, pero sólo es cuestión de tiempo que este navegador también soporte WebP completamente.
- Se deberían ajustar las opciones de guardado (JPEG progresivo o PNG entrelazado) para mostrar una previsualización de la imagen mientras se carga.
- La imagen no debe desplazar el contenido que tiene debajo cuando ésta se carga. Para evitarlo, debemos especificar cualquiera de los tamaños usados con los atributos width y height, pues así el navegador podrá calcular la proporción del alto de la imagen.
- La recomendación del punto anterior no funciona cuando tenemos versiones de navegadores comprendidas entre el 2010 y principios del 2020, cuando dejó de recomendarse usar dichos atributos. Tampoco funciona con determinadas implementaciones de la técnica lazy load y cuando se cambia la proporción de la imagen según el tamaño de la pantalla. En esos casos es mejor utilizar la técnica CSS Ratio Boxes con la que, además, podemos rellenar el hueco donde se mostrará con el color predominante de la imagen.
- Se debe retrasar la carga de las imágenes no críticas (below the fold), para que no interfieran con la descarga de elementos críticos (above the fold). Esto se conoce como carga retrasada de imágenes o lazy load, y consiste en no cargar las imágenes hasta que entran dentro del área de visualización del usuario.
- Normalmente, no se recomienda adelantar la carga de imágenes críticas, ya que las imágenes no son recursos tan críticos como el CSS y el JavaScript, pero veremos cómo podemos adelantar un poco esta carga evitando aplicarles la técnica lazy load.
Implementación técnica
Combinando todas las recomendaciones tendríamos que implementar las imágenes siguiendo estas reglas:
- Escribir el texto SEO del atributo alt de la etiqueta <img>.
- Usar los elementos <picture> y <source> para especificar las imágenes en Webp con el atributo type=»image/webp», manteniendo dentro de <picture> el elemento <img> para aquellos navegadores que no soportan dicho formato, y disponer del texto altenativo.
- Aplicar el atributo srcset en los elemento <img> y <source> para especificar los distintos tamaños de imagen, tanto en Webp como en el formato alternativo. Aquí incluiremos el ancho en píxeles de cada imagen en el formato «[nombre archivo] [ancho]w».
- Incluir los atributos width y height dentro de <img> para reservar la proporción del alto.
- Emplear el atributo loading de <img> para la técnica lazy load.
- Definir los valores del atributo sizes para especificar el ancho que ocupa la imagen en cada corte de la maquetación responsiva, el cual puede ser un tamaño fijo en píxeles o un porcentaje respecto al viewport. Este atributo es necesario para que el navegador sepa el ancho que va a ocupar la imagen antes de cargar el CSS y poder elegir así el tamaño más adecuado de los disponibles en el atributo srcset. Si no se especifica, el navegador supone que ocupa el 100% del viewport y podría cargarse un tamaño más grande de lo necesario.
Además, a modo de ejemplo, se han añadido elementos de maquetación adicionales para implementar la técnica CSS Ratio Boxes, para reservar el alto y poner un color de fondo, antes de que se cargue la imagen, para el caso que el navegador no soporte la reserva de la proporción del alto con width y height (de todas formas dejamos estos atributos para que las PageSpeed no nos ponga una mala nota en caso de carecer de ellos):
<style> .wrap-img{ position:relative; } .wrap-img div{ padding-top:56.5%; background: #ccc; } .wrap-img picture{ position:absolute; top:0; } </style> <div class="wrap-img"> <div></div> <picture> <source type="image/webp" srcset="/img/img600x339.webp 600w, /img/img300x170.webp 300w, /img/img250x141.webp 250w, /img/img1200x678.webp 1200w" sizes="(max-width:768px) 93vw, (max-width: 975px) 59vw, 600px" /> <img width="600" height="339" src="/img/img1200x678.jpg" srcset="/img/img600x339.jpg 600w, /img/img300x170.jpg 300w, /img/img250x141.jpg 250w, /img/img1200x678.jpg 1200w" sizes="(max-width:768px) 93vw, (max-width: 975px) 59vw, 600px" alt="Texto SEO" loading="lazy" /> </picture> </div>
Si la imagen está above the fold, usaremos el atributo loading=»eager» (en lugar de loading=»lazy») para evitar aplicar la carga retrasada por JavaScript.
Lazy load por JavaScript y sin perder indexabilidad
Como el atributo loading no está soportado aún por todos los navegadores, solo si queremos dar soporte a éstos, podemos aplicar la siguiente implementación con JavaScript (sólo a imágenes below the fold).
El código HTML de la imagen con lazy load sería como en el siguiente ejemplo, sustituyendo los atributos src y srcset por atributos data (data-src y data-srcset respectivamente) y añadiendo a <img> la clase que usa la librería para realizar la carga por lazy load (class=»lazyload»). Además añadimos el atributo <noscript>, para no perder indexabilidad, ya que las arañas buscarán las imágenes en los atributos src y srcset, no en data-src y data-srcset:
<picture> <source type="image/webp" data-srcset="/img/img600x339.webp 600w, /img/img300x170.webp 300w, /img/img250x141.webp 250w, /img/img1200x678.webp 1200w" sizes="(max-width:768px) 93vw, (max-width: 975px) 59vw, 600px" /> <img width="600" height="339" data-src="/img/img1200x678.jpg" data-srcset="/img/img600x339.jpg 600w, /img/img300x170.jpg 300w, /img/img250x141.jpg 250w, /img/img1200x678.jpg 1200w" sizes="(max-width:768px) 93vw, (max-width: 975px) 59vw, 600px" alt="Texto SEO" loading="lazy" class="lazyload" /> </picture> <noscript> <picture> <source type="image/webp" srcset="/img/img600x339.webp 600w, /img/img300x170.webp 300w, /img/img250x141.webp 250w, /img/img1200x678.webp 1200w" sizes="(max-width:768px) 93vw, (max-width: 975px) 59vw, 600px" /> <img width="600" height="339" src="/img/img1200x678.jpg" srcset="/img/img600x339.jpg 600w, /img/img300x170.jpg 300w, /img/img250x141.jpg 250w, /img/img1200x678.jpg 1200w" sizes="(max-width:768px) 93vw, (max-width: 975px) 59vw, 600px" alt="Texto SEO" loading="lazy" /> </picture> </noscript>
Código JavaScript:
//si el atributo loading está soportado por el navegador, cambiamos los atributos data por sus homólogos if ('loading' in HTMLImageElement.prototype) { const images = document.querySelectorAll('img[loading="lazy"]'); images.forEach(img => { img.src = img.dataset.src; img.srcset = img.dataset.srcset; //obentemos padre y elementos source var source = img.parentElement.getElementsByTagName("source")[0]; source.src = source.dataset.src; source.srcset = source.dataset.srcset; }); } else { // si no está soportado por el navegador cargamos un polyfill (lazysizes) const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/lazysizes/5.1.2/lazysizes.min.js'; document.body.appendChild(script); }
La librería lazysizes, cuando detecta el bot de Google en el user-agent, muestra todas las imágenes sin lazy load, por lo que Google no tendrá problema en indexar la imagen con esta implementación. El bot de Google verá la imagen en el atributo <noscript> en la primera pasada (sin JavaScript) verá la imagen en el atributo <noscript> y en la segunda pasada (con JavaScript), también la verá.
No obstante, Google no parece tener problemas para indexar otras implementaciones de lazy load menos consideradas con las arañas (sin el atributo <noscript> y sin desactivación de carga retrasada por user-agent), pero si queremos estar realmente seguros de que Google y otras arañas no van a tener problemas, es preferible usar la implementación comentada aquí.
¿Cómo se cargan varios tamaños si la imagen funciona por CSS?
Actualmente, la forma más compatible de cargar una imagen por CSS para todos los navegadores consiste en especificar los distintos tamaños con media queries de resolución.
Por ejemplo:
.ejemplo{ background-image:url(img1x.jpg); } @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { .ejemplo{ background-image:url(img2x.jpg); } }
La unidad usada en el ejemplo (dpi), son dots per inch o puntos por pulgada, en donde una pantalla de resolución simple o con correspondencia 1 a 1 en píxeles CSS, tendría 96 dpis, por lo que en el ejemplo se carga la imagen cuando la pantalla es cómo mínimo de doble resolución (192/2= 96).
Esto tendremos que combinarlo con las media queries que tienen en cuenta el tamaño de la pantalla (max-width o min-width), para cargar los tamaños más adecuados en cada caso.
Esta implementación es mucho más engorrosa que la de HTML y nos puede obligar a establecer la misma imagen varias veces para distintas situaciones. Cuando la especificación de CSS evolucione más, podría cambiar la manera de implementarlo a una más sencilla.
Para combinar este código con imágenes Webp, no queda más remedio que detectar con JavaScript si el navegador soporta Webp y, dependiendo de si lo soporta o no, cambiar la clase usada en las imágenes aplicando reglas distintas de CSS en cada caso.
Por ejemplo, podemos utilizar la librería Modernizr (incluyendo sólo la detección de webp o lo que necesitemos detectar).
Ejemplo:
.no-webp .ejemplo{ background-image:url(img1x.jpg); } .webp .ejemplo{ background-image:url(img1x.webp); }
De forma similar, podemos implementar la técnica lazy load, añadiendo una clase distinta a las imágenes incluidas en el área de visualización por JavaScript.
¿Cómo elegir los tamaños de imagen apropiados?
La maquetación de las imágenes se puede hacer de tres formas:
- Definiendo un tamaño fijo para cada rango de ancho de pantalla.
- Definiendo rangos de ancho de pantalla en los que la imagen ocupe un porcentaje de ésta.
- Combinando las dos opciones anteriores. Esta opción es la utilizada habitualmente, siendo lo más común tener un tamaño fijo en escritorio y un tamaño variable, basado en un porcentaje, en móvil.
Hay que tener en cuenta que en móviles y tablets las imágenes usan píxeles virtuales o píxeles CSS, por lo que, para que se vea perfecta una imagen con 1.200 píxeles de ancho en una tablet grande con pantalla retina de triple resolución, tendría que tener 1.200 x 3 = 3.400 píxeles de ancho. Este es el caso en el que se necesitan imágenes más grandes, pero no vamos a tener en cuenta pantallas de triple resolución, ya que supondría tener imágenes con un peso excesivamente grande y el ojo humano no va a ser capaz de apreciar la diferencia de calidad, pero sí que generaremos versiones para pantallas de doble resolución o inferiores.
Teniendo en cuenta todo esto, podemos definir la estrategia para elegir los distintos tamaños que consiste en seguir los siguientes pasos:
- Si la maquetación usa tamaños fijos, partiríamos generando tantas versiones como tamaños fijos distintos tengamos, de forma que cada una ocupe exactamente cada uno de esos tamaños.
- Si la maquetación usa tamaños variables dependiendo de un porcentaje, habrá que tener en cuenta cuál va a ser el hueco más grande y cuál el más pequeño, que va a ocupar la imagen, a lo largo de cada rango de tamaño de pantalla o cortes responsivos de la maquetación, generando una versión de la imagen para el hueco más grande y otra para el más pequeño en cada rango, sin repetir tamaños que sean muy parecidos.
- Después, para la versión con el tamaño más grande, generaremos una imagen del doble de tamaño, ya que queremos que, en pantallas de doble resolución, se vean correctamente estas imágenes.
- Una vez tenemos todas las versiones de tamaños anteriores, deberíamos intentar generar versiones para los distintos tamaños intermedios, de forma equitativa para que no haya demasiada diferencia entre cada uno de ellos. Aunque aquí, dependiendo del caso, podríamos dar prioridad a generar más tamaños distintos para las versiones que se van a usar en móvil.
Siguiendo estos pasos, podemos llegar a tener aproximadamente entre 6 y 8 versiones de tamaños para una imagen que se tenga que ver ocupando toda la pantalla en móvil. Si no ocupa toda la pantalla, se obtendrán menos tamaños distintos o incluso sólo 1 o 2, si es una imagen pequeña.
Conclusiones
Dada la continua evolución del desarrollo web, si tienes una web, es más que probable que se deba actualizar la forma de incluir imágenes en ésta.
La forma de incluir imágenes responsivas en el código HTML ya está en un estado bastante maduro, cubriendo la lista de características deseables que hemos especificado y alguna más, como poder elegir una imagen con distintas proporciones para cada tamaño de pantalla (caso que no hemos cubierto en este artículo). En cambio, la especificación de CSS, tiene que evolucionar más para poder facilitar la inclusión de imágenes responsivas y en Webp, de forma sencilla y sin tener que usar JavaScript.
y si la imagen tiene un hipervículo, la imagen formato .webp lleva vículo o solo llevaría el vínculo la imagen q tiene la etiqueta img ???
saludos y gracias
Hola Javier,
En ese caso el enlace debería englobar el contenedor