wandres.dev
IMÁGENES · astro:assets

Picture: varios formatos y art direction

Cuándo un solo img no basta y el componente Picture construye un elemento picture con varias fuentes: negociar formatos modernos con formats y un fallbackFormat de reserva, negociar resolución con densities para la densidad de píxeles o con widths y sizes para el ancho de maqueta, y por qué la verdadera art direction, la que cambia el recorte y no solo el peso, se compone a mano con fuentes y condiciones de medio.

⏱ 17 min

<Image /> resuelve el caso común con un único <img> en un único formato. Pero la web moderna quiere más: ofrecer avif al navegador que lo entienda y webp al que no, servir el doble de píxeles a una pantalla retina y la mitad a un móvil modesto, elegir la fuente en función del ancho real de la maqueta. Todo eso lo articula el elemento <picture> del HTML, y <Picture /> es el componente que lo construye por ti: emite varias <source> con sus formatos y sus resoluciones, y un <img> de reserva por si nada encaja. Su oficio no es decidir, sino ofrecer alternativas y dejar que el navegador escoja.

🎯 Al terminar esta lección sabrás
  • Entender por qué ofrecer varios formatos exige un <picture> y no basta un <img>.
  • Negociar formatos con formats y garantizar un fallbackFormat de reserva.
  • Negociar resolución con densities o con widths acompañado de sizes.
  • Reconocer que la art direction real se compone a mano con condiciones de medio.

Del img único al elemento picture

Un <img> apunta a una imagen y a un formato. Si quieres que el navegador prefiera avif, acepte webp como segunda opción y caiga en un jpg universal si no soporta ninguno de los dos, un solo <img> se queda corto: hace falta un <picture> que envuelva varias <source>, cada una con su tipo, y termine en un <img> que sirva de red de seguridad. Escribir ese árbol a mano, con sus srcset y sus type, es tedioso y frágil. <Picture /> lo genera a partir de una lista de formatos.

---
import { Picture } from 'astro:assets';
import hero from '../assets/hero.png';
---
<Picture
  src={hero}
  formats={['avif', 'webp']}
  fallbackFormat="png"
  alt="Vista aérea del puerto al amanecer"
/>

Ese componente no produce un <img>, sino un <picture> con una <source> de tipo avif, otra de tipo webp y un <img> final en png. El navegador recorre las fuentes de arriba abajo y se queda con la primera cuyo tipo entiende; si no entiende ninguna, usa el <img>. Todos los demás props que ya conoces de <Image />alt obligatorio, width, quality— siguen valiendo aquí igual.

El HTML que genera deja ver la lógica con claridad: una fuente por formato con su srcset, y el <img> de cierre que lo sostiene todo.

<picture>
  <source srcset="/_astro/hero.avif" type="image/avif" />
  <source srcset="/_astro/hero.webp" type="image/webp" />
  <img src="/_astro/hero.png" width="1600" height="900"
       decoding="async" loading="lazy" alt="Vista aérea del puerto al amanecer" />
</picture>

Conviene no perder de vista dónde vive cada responsabilidad en ese árbol. Las <source> solo aportan candidatos: rutas y tipos entre los que el navegador elige. El <img> final es la imagen de verdad, la que se muestra y la que porta el alt, el width y el height; sin él, un <picture> no pinta nada. Por eso el fallback no es un extra opcional sino el corazón del elemento, y el texto alternativo, declarado una sola vez en el componente, acaba en ese <img> que siempre está.

formats: ofrecer varios y dejar elegir

El orden de formats es el orden de preferencia. Al poner avif antes que webp declaras: “sirve avif a quien pueda con él, y webp al resto”. Conviene listar primero el más eficiente y moderno, porque es el que ahorra más peso, y dejar los formatos más compatibles para el final de la cascada.

El fallbackFormat es la última red: el formato del <img> que se usa cuando ninguna <source> encaja. Debe ser un formato que entienda cualquier navegador —png o jpg— porque su misión es justamente no fallar nunca. Omitirlo no rompe nada, pues Astro elige uno razonable, pero declararlo deja explícito cuál es tu suelo de compatibilidad.

💡
El orden de formats es la cascada de preferencia

El navegador no compara calidades ni pesos: coge la primera <source> que sabe decodificar y se detiene. Por eso el orden que escribes es una decisión de rendimiento, no de estilo. Poner avif primero significa que las pantallas modernas reciben el formato más liviano; ponerlo último lo desperdiciaría, porque webp, más pesado pero también soportado, ganaría la carrera antes. Ordena siempre del más eficiente al más compatible.

widths, densities y el atributo sizes

Ofrecer formatos es media negociación; la otra media es la resolución. Aquí hay dos estrategias, y elegir la correcta depende de la pregunta que quieras que el navegador responda.

densities sirve para la densidad de píxeles: cuando la imagen se muestra a un tamaño de maqueta fijo pero quieres darle el doble o el triple de píxeles a las pantallas retina. Con densities={[1.5, 2]} Astro genera un srcset con descriptores x, y cada dispositivo baja la variante que corresponde a su densidad.

<Picture
  src={hero}
  formats={['avif', 'webp']}
  densities={[1.5, 2]}
  alt="Vista aérea del puerto al amanecer"
/>

widths sirve para el ancho de maqueta: cuando la imagen ocupa proporciones distintas del viewport según el tamaño de pantalla —ancho completo en móvil, una columna estrecha en escritorio—. Generas varias anchuras reales y, con el atributo sizes, le dices al navegador cuánto espacio ocupará la imagen en cada caso para que elija la fuente justa. Sin sizes, widths no puede decidir bien: son una pareja inseparable.

<Picture
  src={hero}
  formats={['avif', 'webp']}
  widths={[240, 540, 720, 1080]}
  sizes="(max-width: 720px) 100vw, 720px"
  alt="Vista aérea del puerto al amanecer"
/>

No mezcles ambas: densities responde a “cuántos píxeles por punto” y widths a “cuántos puntos de ancho”, y combinarlas produce un srcset ambiguo. Elige el eje según cómo se comporte la imagen en tu maqueta.

flowchart TD
PIC[componente Picture] --> ELEM[elemento picture]
ELEM --> S1[source avif con su srcset]
ELEM --> S2[source webp con su srcset]
ELEM --> IMG[img de reserva en formato universal]
ELEM --> NAV[el navegador escoge la primera que soporta]
style PIC fill:#89b4fa,color:#11111b
style NAV fill:#f9e2af,color:#11111b
style IMG fill:#a6e3a1,color:#11111b

Art direction: cuando cambia el recorte, no solo el peso

Conviene deshacer un malentendido frecuente. <Picture /> negocia formato y resolución, pero sirve la misma imagen en todas las variantes: el mismo encuadre, el mismo recorte, escalado. La art direction es otra cosa: mostrar imágenes distintas según el viewport —un plano amplio y apaisado en escritorio, un recorte vertical y cercano en móvil— porque lo que funciona en una forma no funciona en la otra.

Eso <Picture /> no lo hace solo, y es honesto saberlo. La art direction se compone a mano con un <picture> cuyas <source> llevan una condición media, cada una apuntando a una imagen ya procesada. Para generar esas fuentes procesadas sin el componente usarás getImage, la herramienta de la próxima lección; la estructura, en cambio, es este <picture> explícito.

<picture>
  <source media="(max-width: 640px)" srcset={movilWebp} type="image/webp" />
  <source media="(min-width: 641px)" srcset={anchoWebp} type="image/webp" />
  <img src={anchoFallback.src} alt="Vista aérea del puerto al amanecer" />
</picture>

La frontera es nítida: si solo cambia el peso o la nitidez de la misma imagen, <Picture /> te lo resuelve con formats, densities o widths. Si cambia la imagen misma según la pantalla, entras en art direction y compones el <picture> tú, con las condiciones media que tu diseño pida.

🎞️

formats

Una source por formato, en orden de preferencia. El navegador coge la primera que entiende.

🛟

fallbackFormat

El formato del img de reserva. Debe ser universal, porque su papel es no fallar nunca.

🔎

densities frente a widths

densities negocia por densidad de píxeles con descriptores x; widths negocia por ancho de maqueta con sizes.

🖼️

Art direction

Cambiar el recorte según el viewport se compone a mano con source y media, no con Picture solo.

Picture es negociación declarativa: describes lo posible y el borde decide

Lo que de verdad enseña <Picture /> no es una lista de props, sino una forma de pensar el rendimiento que vale mucho más allá de las imágenes. El servidor, en el momento del build, ignora casi todo lo que importa para elegir la mejor imagen: no sabe el ancho exacto del viewport de quien visita, ni la densidad de su pantalla, ni qué formatos soporta su navegador, ni si su red va sobrada o ahogada. Esa información solo existe en el otro extremo, en el instante de la petición, y solo el navegador la tiene entera. La tentación ingenua sería intentar adivinar en el servidor y servir una única imagen “buena para casi todos”, que es como no servir la buena para casi nadie. <Picture /> renuncia a adivinar y hace algo más inteligente: no decide, describe. Enumera todas las variantes razonables —estos formatos, estas resoluciones, este fallback— y delega la elección final en quien está en posición de acertar, el navegador, que conoce su propio contexto y escoge en un instante la fuente óptima. Ese patrón tiene nombre: negociación declarativa. En lugar de codificar una decisión, codificas el espacio de decisiones y dejas que se resuelva en el borde, lo más tarde posible, cuando ya se sabe todo lo que hacía falta saber. Es la misma sabiduría que rige el diseño responsivo, la mejora progresiva y buena parte de la web robusta: no impongas desde el centro lo que el borde puede decidir mejor con información que tú nunca tendrás. Cuando escribes un <Picture /> no estás optimizando una imagen, estás declarando un contrato de posibilidades y confiando su cumplimiento a quien mejor puede honrarlo. Interioriza ese giro —de decidir por todos a describir para cada uno— y habrás ganado algo que reutilizarás en cada capa de una aplicación bien pensada.

⚔️ Ofrece alternativas y deja elegir al navegador
  1. Sustituye un <Image /> por un <Picture /> con formats={['avif', 'webp']} y un fallbackFormat, e inspecciona el <picture> generado para ver las <source> y el <img> de reserva.
  2. Añade densities={[1.5, 2]} y examina el srcset con descriptores x; luego cámbialo por widths más sizes y compara cómo se transforma ese srcset.
  3. Reduce la ventana del navegador y observa en las herramientas de red qué variante descarga en cada tamaño.
  4. Compón a mano un <picture> con dos <source> y sus condiciones media para servir un recorte vertical en móvil y uno apaisado en escritorio, y razona por qué eso es art direction y no una simple negociación de resolución.