CategoríaCódigo y arquitectura

Imágenes en la web: tamaño, formato y cuándo cargarlas

9 minutos de lectura

Una imagen liviana depende de cuatro decisiones en el HTML. Entregar un archivo del tamaño en que se muestra, con srcset y sizes. Usar un formato moderno, como WebP o AVIF. Declarar ancho y alto para que el navegador reserve el espacio. Y elegir cuándo cargar: loading="lazy" solo para lo que queda bajo el pliegue, prioridad alta para la imagen principal de arriba (web.dev, carga diferida en el navegador). La última es la que más engaña, porque un atributo en el lugar equivocado atrasa justo la imagen que la persona ve primero.

Por qué la imagen pesa tanto

Según el HTTP Archive, citado en el artículo de web.dev sobre carga diferida, la imagen es el recurso más solicitado en la mayoría de los sitios y el que más ancho de banda consume. Entre el 10% de sitios más pesados en este aspecto, cada uno envía más de 5 MB de imágenes, tanto en computadora como en celular.

El peso no es el único problema. La imagen grande de arriba muchas veces es el elemento que mide el Largest Contentful Paint, o LCP, la métrica que marca cuándo apareció lo principal de la página. Una imagen atrasada ahí atrasa toda la métrica.

Qué imagen merece el peso y dónde va

Antes de optimizar, conviene preguntarse si la imagen tiene que estar ahí. En los estudios de seguimiento ocular del NN/g, las personas examinan las fotos que aportan información, como productos y gente real, e ignoran las que solo decoran la página (Jakob Nielsen, NN/g (en inglés)). Una imagen decorativa cuesta bytes y no devuelve atención.

Después viene la posición. En un estudio de 2018 del NN/g, las personas pasaron cerca del 57% del tiempo de lectura en la primera pantalla y el 74% en las dos primeras (Therese Fessenden, NN/g (en inglés)). Por eso la imagen de arriba merece prioridad y el resto puede esperar al desplazamiento.

La dificultad es que la primera pantalla cambia de un dispositivo a otro. Un diseño adaptable tiene dos, tres o más pliegues, uno para cada tamaño de pantalla (Amy Schade, NN/g (en inglés)). Quien marca las imágenes en el HTML tiene que decidir sin saber en qué pantalla se abrirá la página.

Un archivo para cada ancho de pantalla

La guía de imágenes adaptables de MDN separa dos problemas. El primero es el cambio de resolución: la misma foto, en archivos de distintos anchos. El segundo es la dirección de arte: otro recorte de la foto para otra pantalla.

Para el cambio de resolución bastan dos atributos en la etiqueta <img>:

  • srcset enumera los archivos disponibles y el ancho real de cada uno, como foto-480.jpg 480w.
  • sizes indica cuánto espacio ocupará la imagen en pantalla en cada condición, como (max-width: 480px) 440px.

Con eso el navegador elige el archivo más pequeño que todavía llena el espacio con nitidez. En el ejemplo de MDN, la versión de 800 píxeles pesa 128 KB y la de 480 píxeles pesa 63 KB (MDN). En una página con decenas de fotos, esa diferencia se acumula.

La misma guía explica por qué esto va en el HTML y no en un script. El navegador empieza a descargar las imágenes antes de leer el CSS y el JavaScript de la página. Cuando un script averiguara el ancho de la pantalla, el archivo grande ya estaría en camino (MDN).

Para la dirección de arte, la herramienta es el elemento <picture>, con un <source> por recorte y un <img> al final como respaldo.

Formato moderno, con respaldo

WebP y AVIF guardan la misma foto en menos bytes que JPEG y PNG. El <picture> permite ofrecer el formato nuevo y mantener el antiguo para los navegadores que no lo aceptan. Cada <source> declara su tipo en el atributo type, y el navegador salta lo que no sabe leer (MDN).

Quien usa Astro no necesita armarlo a mano. El componente <Picture /> genera AVIF y WebP a partir de un PNG y deja el original como respaldo. El <Image /> convierte a WebP por defecto. Eso vale solo para imágenes guardadas en src/; lo que está en public/ sale tal cual (documentación de Astro sobre imágenes).

Reservar el espacio antes de que llegue la imagen

Sin width y height, el navegador no conoce el tamaño de la imagen hasta que llega. El texto de abajo sube y luego baja, y la persona pierde el punto de lectura. Por eso web.dev recomienda declarar las dos medidas en cada <img> (web.dev). El CSS todavía puede hacer flexible la imagen, y la proporción se conserva. Lo que mide ese salto es el CLS, tema del texto sobre estados de carga.

El mismo artículo trae un caso menos obvio. En una galería sin medidas, cada imagen empieza con 0 por 0 píxeles. Todas parecen caber en pantalla a la vez, y el navegador descarga la galería entera, aunque la carga diferida esté activada (web.dev).

El componente <Image /> de Astro completa el ancho y el alto solo para imágenes locales (Astro).

Carga diferida, pero solo bajo el pliegue

El atributo loading="lazy" aplaza la descarga hasta que la imagen se acerca al área visible. Funciona sin biblioteca de JavaScript. La advertencia del propio web.dev es directa: no lo uses en imágenes que probablemente aparecen en la primera pantalla, sobre todo en la que se vuelve el LCP (web.dev). El navegador solo aplaza una imagen cuando sabe dónde queda en la página, y así la imagen más importante de la página queda para después.

WordPress lo comprobó en la práctica. Empezó a marcar casi todas las imágenes como diferidas, incluidas las de arriba. En una prueba de laboratorio con el tema Twenty Twenty-One, desactivar la carga diferida dejó el LCP de las páginas de archivo entre 13% y 15% más rápido (web.dev, efectos de demasiada carga diferida). A cambio, los bytes de imagen se multiplicaron por más de dos.

La corrección probada se quedó con lo mejor de los dos lados. Cargar de forma normal solo la primera imagen destacada y dejar el resto diferido deshizo la pérdida en el LCP y mantuvo el ahorro de bytes del comportamiento por defecto (web.dev).

Prioridad para la imagen principal

Escribir loading="eager" no acelera nada. Es solo el comportamiento normal del navegador, sin retraso adicional. Para pedir que una imagen llegue antes que las demás, el atributo es fetchpriority="high" (web.dev, API Fetch Priority).

La razón está en el orden de trabajo de Chrome. Las imágenes del área visible suelen empezar con prioridad baja y solo suben cuando el diseño de la página está listo. Marcar la principal se salta esa espera. En la página de Google Vuelos, la imagen de fondo con prioridad alta llevó el LCP de 2,6 a 1,9 segundos (web.dev).

El atributo también sirve para bajar la prioridad. En un carrusel, solo la primera imagen se ve de entrada. Las demás pueden llevar fetchpriority="low" para no competir con ella por la red (web.dev). Los cuidados de diseño del carrusel están en el texto sobre carruseles en el e-commerce.

Cómo aparece en mi trabajo

En kahue.net, cada imagen tiene un papel distinto, y el código sigue ese papel.

El retrato de mi página sobre mí se muestra con 200 píxeles de ancho, y el archivo WebP tiene 400. En una pantalla de alta densidad, el navegador usa píxeles que ya existen en lugar de estirar la imagen. Como el retrato está arriba, lleva width y height, carga normal y fetchpriority="high".

En los casos, como el de Vexor, la imagen grande justo debajo del título sale en tres anchos (768, 1040 y 1400 píxeles), con sizes siguiendo el ancho de la columna de la página. Está marcada como eager, porque el componente de imagen de Astro genera loading="lazy" cuando no se indica nada (referencia de Astro). Las miniaturas de la galería quedan más abajo. Por eso salen en 320 y 640 píxeles, con carga diferida.

Cada miniatura es un enlace a la pantalla completa en WebP, generada al compilar el sitio. Sin JavaScript, el clic abre la imagen. Con JavaScript, abre la galería superpuesta. Antes, ese enlace llevaba al archivo original en PNG, mucho más pesado.

Todavía falta algo. La portada de los casos pide carga normal, pero no pide prioridad alta. Según la referencia de Astro, la propiedad priority resuelve eso de una vez para las imágenes de arriba. No medí el efecto de ese cambio, y el texto no le atribuye ningún número.

Dónde difieren las fuentes

Los dos artículos de web.dev parecen discrepar sobre el riesgo de la carga diferida. El primero dice que, en Chrome para Android con 4G, el 97,5% de las imágenes diferidas terminó de cargar hasta 10 milisegundos después de volverse visibles (web.dev). Visto así, el retraso casi no existe.

El segundo encontró, en los datos del HTTP Archive, un LCP mediano de 2.922 milisegundos en páginas sin carga diferida y de 3.546 en las que la usan (web.dev). Los autores avisan que es correlación, no causa.

Las dos cosas encajan. El primer número mide imágenes fuera de la pantalla, que el navegador busca con anticipación mientras la persona se desplaza. El segundo capta el caso en que la carga diferida cayó en la imagen de arriba, que no tiene ninguna anticipación.

El error común: dejar que el CSS achique la foto

El error más frecuente es exportar la imagen con el ancho del archivo original y dejar que el CSS la reduzca en pantalla. Se ve bien en cualquier dispositivo, y por eso nadie lo nota. Pero el celular descarga los píxeles de un monitor grande para mostrar una fracción de ellos. MDN lo llama desperdicio de ancho de banda, pagado por quien usa datos móviles. La verificación es simple: abre la pestaña Red de las herramientas del navegador en una pantalla angosta y mira qué archivo llegó.

Fuentes

Más textos

Ver todos los textos