wandres.dev
IMÁGENES I · Formatos y compresión

El elemento picture y su orden de evaluación

El algoritmo exacto con el que el navegador elige una fuente, por qué nunca compara tamaños de archivo, y las dos tareas distintas que picture resuelve con la misma sintaxis.

⏱ 18 min

picture no es un elemento de imagen mejorado: es un envoltorio de selección que decide qué conjunto de fuentes usará el elemento de imagen que lleva dentro. Su algoritmo es corto, determinista y no hace nada inteligente, y precisamente por eso hay que conocerlo: el navegador se queda con la primera fuente que puede usar, no con la mejor.

🎯 Al terminar esta lección sabrás
  • Recitar el algoritmo de selección paso a paso y saber en qué punto se detiene.
  • Explicar por qué el orden de las fuentes es tu ranking de preferencia y no una sugerencia.
  • Separar la negociación de formato de la dirección de arte y saber cuándo combinarlas.
  • Colocar cada atributo en el elemento correcto: el envoltorio, la fuente o la imagen.

El algoritmo, paso a paso

El navegador recorre los elementos source hijos en orden de documento y, para cada uno, aplica esta secuencia:

  1. Si no tiene atributo srcset, se salta.
  2. Si tiene atributo media y la consulta no coincide con el estado actual, se salta.
  3. Si el srcset no produce ninguna candidata válida, se salta.
  4. Si tiene atributo type y el navegador no sabe decodificar ese tipo, se salta.
  5. Si ha llegado hasta aquí, esta es la fuente elegida y la búsqueda termina.

Si ningún source supera los cuatro filtros, se usa el srcset o el src del propio elemento de imagen.

Tres consecuencias que hay que interiorizar.

El navegador nunca compara tamaños de archivo. No descarga dos candidatas para ver cuál pesa menos. La eficiencia relativa de AVIF frente a WebP no participa en la decisión: participa tu orden.

La búsqueda es perezosa y se detiene en la primera coincidencia. Poner la fuente JPEG en primer lugar significa que nadie verá jamás las otras, porque todos los navegadores saben decodificar JPEG.

Un type desconocido descarta la fuente sin coste. Es lo que hace segura la selección de formatos: un navegador sin AVIF ni siquiera intenta la petición.

<picture>
  <!-- 1. El mas eficiente primero. -->
  <source type="image/avif" srcset="/img/foto.avif">
  <!-- 2. La reserva casi universal. -->
  <source type="image/webp" srcset="/img/foto.webp">
  <!-- 3. El elemento de imagen es la ultima reserva y ES el elemento. -->
  <img src="/img/foto.jpg" alt="Descripcion" width="1200" height="800">
</picture>

El orden correcto es siempre del formato más eficiente al menos eficiente, y el último recurso, el src del elemento de imagen, tiene que ser un formato que entienda absolutamente todo.

El elemento de imagen es el elemento

Este es el segundo malentendido más común, después del orden. picture y source no producen ninguna caja: no se pintan, no ocupan espacio, no se estilan. Todo lo que ves y todo lo que manipulas es el elemento de imagen que hay dentro, y es obligatorio: sin él, un picture no muestra nada.

De ahí sale el reparto de atributos, que conviene tener claro porque colocarlos mal no da error, simplemente no hace nada:

Atributo Dónde va
srcset, sizes En source, y también en img como reserva
type Solo en source
media Solo en source
src Solo en img
alt Solo en img
width, height En img, y en source si cambia la proporción
loading, decoding Solo en img
fetchpriority Solo en img
class, style En img, que es lo que se pinta

Los atributos de dimensión sobre source existen para el caso de la dirección de arte con proporciones distintas y su soporte es más reciente que el del resto; si tus variantes comparten proporción, con declararlas en el elemento de imagen basta.

Y una consecuencia práctica del reparto: en el DOM, img.currentSrc te dice qué fuente eligió realmente el navegador, que es la única forma fiable de comprobar que tu orden está haciendo lo que crees.

// Que fuente se llevo el gato al agua, y en que formato.
document.querySelectorAll('picture img').forEach((img) => {
  const listo = () => console.log(
    img.currentSrc.split('/').pop(),
    '|', img.naturalWidth + 'x' + img.naturalHeight,
  );
  img.complete ? listo() : img.addEventListener('load', listo, { once: true });
});

Si esperabas AVIF y ves un .jpg, tienes un orden mal puesto, un type mal escrito o un navegador sin soporte. Los tipos hay que escribirlos exactos: image/avif, image/webp, image/jpeg, image/png. Un image/jpg no existe y descarta la fuente en silencio.

Las dos tareas distintas

picture resuelve dos problemas que comparten sintaxis y no comparten nada más. Mezclarlos sin darse cuenta es la fuente de la mayoría de los marcados inmanejables.

Negociación de formato. El contenido de la imagen es el mismo en todas las fuentes; lo único que cambia es la codificación. Se controla con type. Es una decisión de rendimiento pura y no tiene consecuencias de diseño.

Dirección de arte. El contenido cambia: en escritorio, un plano general apaisado; en móvil, un recorte vertical centrado en el sujeto. Se controla con media. Es una decisión de diseño que además ahorra bytes.

<picture>
  <!-- Movil: recorte vertical, con sus dos formatos. -->
  <source media="(max-width: 700px)" type="image/avif" srcset="/img/hero-vertical.avif">
  <source media="(max-width: 700px)" type="image/webp" srcset="/img/hero-vertical.webp">
  <!-- Escritorio: plano general. -->
  <source type="image/avif" srcset="/img/hero-ancho.avif">
  <source type="image/webp" srcset="/img/hero-ancho.webp">
  <img src="/img/hero-ancho.jpg" alt="Vista aerea del puerto" width="1600" height="900">
</picture>

Fíjate en la explosión combinatoria: dos recortes por dos formatos son cuatro fuentes, y si además necesitas tres anchos por fuente, tienes doce archivos por imagen. Es correcto y es caro, y es el argumento más fuerte a favor de negociar el formato en el servidor, que veremos en la negociación por cabecera Accept: con el formato fuera del marcado, esas cuatro fuentes vuelven a ser dos.

Fíjate también en que las fuentes con media van primero. Un source sin media coincide siempre, así que cualquier fuente que pongas después de él es inalcanzable. La consulta de medios más restrictiva va arriba; el caso general, abajo.

⚠️
Una consulta de medios que no coincide no cae en la fuente siguiente del mismo formato: cae en la siguiente que coincida, sea cual sea

Es un error sutil y produce el resultado contrario al buscado. Si escribes la fuente AVIF con media de móvil, y a continuación la fuente WebP con media de móvil, y después la AVIF de escritorio, un navegador de escritorio con AVIF se salta las dos primeras por la consulta y se lleva la tercera: correcto. Pero un navegador móvil sin AVIF se salta la primera por el tipo y se lleva la segunda: también correcto. El fallo aparece cuando agrupas por formato en lugar de por consulta: todas las AVIF primero y todas las WebP después. Entonces un navegador móvil con AVIF se lleva la AVIF de escritorio, porque es la primera que coincide en tipo y no tiene media que lo impida. Agrupa siempre por consulta de medios, y dentro de cada grupo ordena por formato.

Cuándo no hace falta picture

Merece decirlo porque el marcado que se ahorra es marcado que no puede estar mal.

Si solo cambia la resolución, no hace falta: el atributo srcset sobre el elemento de imagen basta y es mucho más corto.

Si negocias el formato en el servidor, tampoco: una URL, un elemento de imagen, y el formato se decide en el borde.

Si la imagen es un gráfico vectorial, tampoco: un SVG no tiene variantes de resolución ni de formato.

picture es la herramienta correcta cuando necesitas decidir el formato en el cliente o cuando necesitas dirección de arte. Fuera de esos dos casos añade nodos y ocasiones de equivocarse.

Un último apunte que importa para la métrica: el escáner de precarga sí lee los elementos source dentro de un picture en el HTML en bruto, así que un hero envuelto en picture se descubre igual de pronto que uno suelto. Es una de las pocas construcciones de indirección que no cuesta un viaje de red, y es la razón de que sea aceptable usarla en el elemento que decide el LCP.

El navegador no reevalúa las fuentes a la baja, y por eso tu prueba de redimensionar la ventana te engaña siempre

Cuando pruebas un picture arrastrando el borde de la ventana, ves que al ensanchar cambia a la fuente grande y concluyes que la selección funciona. Después estrechas la ventana, ves que no vuelve a la pequeña, y sospechas que algo está roto. No lo está: es deliberado y está en la especificación. El navegador ya tiene en memoria una imagen de más resolución que la necesaria, y bajar a una menor significaría hacer una petición nueva para mostrar algo peor, así que no lo hace. La consecuencia práctica, y la trampa, es que cualquier prueba manual redimensionando la ventana mide un estado contaminado por lo que ya se descargó antes, y te hará creer que un marcado roto funciona o al revés. La única prueba válida es recargar la página en cada tamaño con la caché desactivada, comprobando currentSrc después de cada recarga. Hay un segundo efecto de esta regla que sí es un problema de rendimiento real: en una interfaz donde un panel lateral se pliega y despliega, o donde una imagen pasa de miniatura a vista ampliada, el navegador acumula en memoria la variante más grande que haya hecho falta alguna vez y no la suelta, lo que en una galería con muchas imágenes y muchos cambios de tamaño se nota en el consumo de memoria del proceso mucho antes que en la red.