Las cosas ya no son lo que eran. Hasta ahora poner una imagen en una Web venía a ser tan fácil como usar una etiqueta img
y si estabas a tope con el SEO le ponías un atributo alt
. Ahora los usuarios notan mucho más cuando algo no se ha hecho con mimo y Google no te pasa ni una: tienes que cuidar el rendimiento de cada byte que se transfiere, así como la accesibilidad, las buenas prácticas y obviamente la optimización para buscadores.
Y estamos hablando de poner una imagen. Lo que hace años hubiera sido literalmente arrastrar y soltar un archivo en Frontpage o Dreamweaver, se ha convertido en una tarea a la que se le puede dedicar una cantidad absurda de tiempo. Pero vamos al grano; poner una imagen hoy puede ser una tarea compleja y podría llegar a constar de estos puntos:
- El punto de partida
- Los formatos
- Las variantes de tamaño (responsive)
- La vista previa
- La transición a la versión completa
El punto de partida
No nos vamos a liar, una imagen básica sería algo así:
<img src="image.jpg" alt="Texto alternativo" />
Según en qué formato te pasen el archivo empezarán a saltar alertas: usa formatos modernos. Esto viene a ser básicamente usar imágenes en formato WebP o traducido para no expertos: Google ha hecho un formato que ofrece beneficios relativos y limitaciones objetivas. Si no lo utilizas podría bajarte puntos que afectarán a tu posicionamiento primero y a tu negocio después. Y para variar tenemos el lastre permanente de Apple, que no siempre tiene soporte para este formato.
En definitiva, deberás tener tu imagen en formato tradicional (jpg habitualmente, png o gif según el caso) y además en WebP. ¿Y cómo se muestra eso? Pues más o menos el código anterior crecería hasta ser algo así:
<picture>
<source srcset="image.webp" type="image/webp">
<source srcset="image.jpg" type="image/jpeg">
<img src="image.jpg" alt="Texto alternativo">
</picture>
El navegador irá por orden y escogerá el primer source
que sea compatible. image/webp
normalmente e image/jpg
en la mayoría de dispositivos de Apple (a día de hoy). Si el navegador ni siquiera soporta las etiquetas picture
/ source
, las omitirá y cargará directamente la img
.
Las variantes de tamaño
También habría que tener en cuenta que hay dispositivos de muchos tamaños diferentes y para tener un buen rendimiento no debería cargar el mismo archivo a todos. Por ejemplo en un teléfono se debería mostrar una imagen más pequeña, mientras que en una pantalla grande debería ser mayor para que no necesitara estirarla perdiendo calidad.
Otro punto a tener en cuenta es que hay pantallas con diferentes densidades de pixeles aunque no cambien de resolución, por ejemplo los teléfonos suelen tener mayor densidad que un ordenador, en torno al doble o el triple. Esto no lo voy a aplicar en el ejemplo pero su atributo media
podría ser (min-resolution: 300dpi)
(si conocemos la densidad) ó (min-resolution: 2dppx)
(si conocempos el ratio). Más información en MDN.
Por ejemplo:
<picture>
<source srcset="image-desktop.webp"
type="image/webp" media="(min-width:650px)" />
<source srcset="image-mobile.webp"
type="image/webp" media="(max-width:649px)" />
<source srcset="image-desktop.jpg"
type="image/jpeg" media="(min-width:650px)" />
<source srcset="image-mobile.jpg"
type="image/jpeg" media="(max-width:649px)" />
<img src="image.jpg" alt="Texto alternativo" />
</picture>
La vista previa
Hasta este punto la cosa se ha puesto engorrosa porque hemos pasado de un archivo de imagen a 4 ó 5 y de una linea de HTML a 11, pero no es complejo. La cosa se empieza a complicar ahora y ya no tendremos suficiente con la ruta de cada archivo y unas cuantas etiquetas HTML.
Para lo que sigue se necesitaría por un lado conocer más información sobre la imagen (tamaño, versión ultra-reducida o color) y por otro utilizar Javascript. ¿Por qué? Pues sencillamente porque empezamos a escuchar eventos del DOM y no tenemos otra forma de hacerlo.
El concepto es muy simple: Se muestra un aperitivo antes de servir la imagen real y con el objetivo de que el cerebro identifique que hay una imagen, el espacio que ocupa y algo mas (un color, una silueta, una versión borrosa de la propia imagen, etc). Y ya de paso si puede ser con una transición para suavizarlo todo, pues mejor.
Ejemplo 1 de 2: Placeholder de color y tamaño.
See the Pen
Image color placeholder by Josep Viciana (@emmgfx)
on CodePen.
Ejemplo 2 de 2: Placeholder con imagen ultra reducida y tamaño.
See the Pen
Image blurry placeholder by Josep Viciana (@emmgfx)
on CodePen.
No voy a copiar aquí el código porque está disponible en el propio Codepen y de lo contrario el post se haría todavía más largo. Además para ilustrar el ejemplo y por mi salud mental he omitido los dos puntos anteriores sobre tamaños y formatos ya que haría los ejemplos mucho más complejos y no aportaría nada nuevo.
Los dos ejemplos hacen básicamente lo mismo, con la salvedad de que el primero muestra un color y el segundo una imagen ultra reducida mientras se está cargando la original. La segunda opción puede parecer más interesante, pero creo que es subjetivo y que en cada habría que plantear si es mejor utilizar un sistema, el otro o ninguno.
Consideraciones importantes
- Los datos deben venir del servidor o ser estáticos: necesitamos saber tanto el tamaño como el color antes de empezar a cargar la imagen real.
- El servidor no podrá procesar la imagen para conocer el tamaño y el color o la vista previa en tiempo real, esta información tendrá que estar procesada previamente y almacenada en una base de datos.
- En el caso de la segunda opción, almacenar esa cantidad de texto (una imagen en base64) en una base de datos podría implicar problemas de rendimiento.
- La segunda opción además incrementa gravemente el tamaño del HTML.
La transición a la versión completa
Ya tenemos una imagen que necesita diferentes formatos, diferentes tamaños e información extra para una previsualización rápida. También estamos escuchando eventos del DOM para cambiar de la previsualización a la real. Ahora vamos a mejorar esta última parte; además de escuchar el evento intentaremos conocer si antes del evento la imagen ya estaba cargada en caché, para así evitar una animación contraproducente (no queremos indicar que la imagen se ha cargado si ya estaba cargada).
¿Cómo podemos saber si la imagen está cargada? El DOM nos proporciona por un lado el evento onLoad
y por otro la propiedad complete
, que nos permitirá saber en cada momento si está cargada o no.
¿Y cómo sé si estaba en caché? pues por el orden de los acontecimientos. Si la propiedad complete es positiva antes de recibir el evento onLoad, es que estaba cargada. Si el evento llega después es que ha habido un proceso de carga y no estaba en caché.
Y para terminar por todo lo alto, vamos a mezclar algo de CSS y algo de React con Hooks. Allá voy.
Al hook lo llamaré useImageLoaded
y sería algo así:
import { useState, useEffect, useRef } from "react";
const useImageLoaded = () => {
const [loaded, setLoaded] = useState(false);
const [preloaded, setPreloaded] = useState(false);
const ref = useRef()
const onLoad = () => {
setLoaded(true)
}
useEffect(() => {
if (ref.current && ref.current.complete) {
if(!loaded){
setPreloaded(true);
}
}
});
return [ref, preloaded, loaded, onLoad];
}
export default useImageLoaded;
Y para usarlo sería así:
...
const [ref, preloaded, loaded, onLoad] = useImageLoaded();
...
<img
ref={ref}
src="..."
className={classNames({ loaded, preloaded })}
onLoad={onLoad}
/>
Lo cual generaría un HTML que podría ser así:
<img src="..." class="" /> <!-- No está cargada -->
<img src="..." class="loaded" /> <!-- Está cargada, no estaba en caché -->
<img src="..." class="loaded preloaded" /> <!-- Está cargada, sí estaba en caché -->
De nuevo he obviado los dos primeros puntos y ahora tampoco estoy incluyendo casi nada del punto anterior. A estas alturas la salud mental ya está en peligro y no hay necesidad de forzar de más.
Ahora viene cuando tenemos que aprovechar la información que tenemos a través de las clases desde el código (S)CSS.
img{
opacity: 0; // La opacidad por defecto
transition: opacity .3s; // La transición por defecto
// Este modificador se aplicará cuando la imagen esté en caché
// para eliminar la transición de opacidad:
&.preloaded{
transition: opacity 0s;
}
// Este modificador se aplicará cuando la imagen esté cargada
// para modificar la opacidad
&.loaded{
opacity: 1;
}
}
¿Y qué podríamos hacer con esto? Pues algo como lo que muestro en este video, funcionando en un side project personal:
Y poco más, esto vendría a ser todo lo que quería explicar. Hemos tocado varias áreas que incrementan la dificultad y que difícilmente sabrán apreciar ni tus clientes, ni tus jefes y a veces ni tus compañeros, pero espero que te hayan parecido interesantes. ¿Son técnicas adecuadas? eso es algo que hay que valorar en cada caso. A veces no es necesario, a veces puede ser contraproducente y a veces sencillamente imposible por motivos técnicos.