Guías
Imágenes responsivas con srcset — Explicado correctamente
srcset y sizes sin el lío. Cómo elige el navegador, qué hacen los descriptores w y x y los patrones que funcionan en producción.
La sintaxis de imágenes responsivas lleva casi una década en HTML y la mayoría de desarrolladores sigue copiándola de Stack Overflow sin entender del todo qué pasa. Está bien hasta que algo se rompe — se descarga el tamaño equivocado, un hero se ve borroso o el navegador elige el asset 2x en un dispositivo 1x. Esta guía cubre qué hacen realmente srcset y sizes, cuándo usar descriptores w o x y los patrones que aguantan en producción.
El problema que resuelven las imágenes responsivas
Tu sitio tiene una imagen hero. En un móvil de 400 px se renderiza a 400 px de ancho. En un escritorio 4K se renderiza a 1920 px. En una tablet Retina se renderiza a 1024 px CSS pero 2048 px reales.
Sin imágenes responsivas, mandas un único archivo. O mandas uno grande que el móvil desperdicia en ancho de banda, o mandas uno pequeño que el 4K pone borroso. No hay respuesta correcta.
srcset y sizes te permiten servir varias versiones y que el navegador elija la correcta por dispositivo.
Descriptores de anchura (sintaxis w)
La forma más común:
<img
src="/images/hero-800.jpg"
srcset="/images/hero-400.jpg 400w,
/images/hero-800.jpg 800w,
/images/hero-1600.jpg 1600w,
/images/hero-2400.jpg 2400w"
sizes="(max-width: 600px) 100vw,
(max-width: 1200px) 80vw,
1200px"
alt="Imagen hero"
width="1200"
height="600">
Están pasando tres cosas:
srcsetlista candidatos. Cada entrada dice “aquí hay un archivo y su anchura real en píxeles es X”. El sufijowsignifica “este archivo tiene X píxeles de ancho”. No es una media condition — solo se le dice al navegador el ancho intrínseco de cada opción.sizesdice al navegador cuánto ocupará la imagen en píxeles CSS en cada breakpoint. “A max-width 600px la imagen ocupa 100vw; a max-width 1200px ocupa 80vw; si no, 1200px”.- El navegador calcula. Mira el viewport, saca de
sizescuántos píxeles CSS ocupará, multiplica por la densidad y elige delsrcsetel archivo más pequeño que cumple o supera ese número de píxeles.
En un iPhone de 390 px con densidad 3x: la imagen es 100vw = 390 CSS px. Píxeles reales = 390 × 3 = 1170. El navegador elige hero-1600.jpg (el más pequeño ≥ 1170). En escritorio 2560 px a 1x: imagen 1200 CSS px × 1 = 1200 reales. Elige también hero-1600.jpg. En escritorio 2560 px a 2x: 1200 × 2 = 2400 reales. Elige hero-2400.jpg.
Descriptores de densidad (sintaxis x)
La otra forma:
<img
src="/images/logo.png"
srcset="/images/logo.png 1x,
/images/logo@2x.png 2x,
/images/logo@3x.png 3x"
alt="Logo">
Es más simple y solo funciona cuando la imagen se renderiza a un único tamaño CSS fijo, independiente del viewport. Logos, iconos y elementos UI con dimensiones fijas son el caso canónico. 1x es tu asset de densidad estándar, 2x tiene el doble de píxeles para Retina, 3x para los móviles que lo quieren.
No puedes mezclar descriptores w y x en el mismo srcset. Elige uno por elemento.
Cuándo usar cada uno
Usa descriptores w cuando:
- La imagen cambia de tamaño con el viewport (la mayoría de heros, imágenes de cuerpo, fotos de producto).
- Quieres que el navegador balancee entre tamaño de archivo y resolución según viewport y densidad.
- Te importa el ancho de banda en móvil.
Usa descriptores x cuando:
- La imagen tiene un tamaño CSS fijo independiente del viewport (iconos UI, logos, avatares, miniaturas en rejilla donde cada celda es igual).
- No necesitas
sizesporque el tamaño CSS es constante. - La simplicidad importa más que la eficiencia perfecta.
El error más habitual es usar x en imágenes responsivas. El navegador no puede tomar buenas decisiones si no sabe cuánto ocupará la imagen.
El atributo sizes, desmitificado
sizes es una lista de media conditions emparejadas con tamaños de imagen, evaluadas en orden. Gana la primera que matchea. Un tamaño final sin condición es el valor por defecto.
sizes="(max-width: 600px) 100vw,
(max-width: 1200px) 80vw,
1200px"
Se lee así: “si el viewport es ≤600px, la imagen ocupa 100vw. Si no, si es ≤1200px, 80vw. Si no, 1200px fijos.”
Errores que he visto repetidas veces:
sizes="100vw"en una imagen que nunca es 100vw. Le estás diciendo al navegador que descargue el archivo más grande porque afirmaste que la imagen llena el viewport. Si tu diseño limita la imagen a 800 px, dilo.- Olvidar
sizes. Sin él, los navegadores asumensizes="100vw". Mismo problema. - Escribir
sizesal revés. Las condiciones conmin-widthvan de mayor a menor. Conmax-width, de menor a mayor. Equivocar el orden hace que la primera condición siempre matchee y las posteriores estén muertas.
El tag <picture> para cambio de formato
srcset maneja variantes de tamaño. <picture> maneja variantes de formato.
<picture>
<source type="image/avif"
srcset="/img/hero-800.avif 800w, /img/hero-1600.avif 1600w"
sizes="(max-width: 900px) 100vw, 1200px">
<source type="image/webp"
srcset="/img/hero-800.webp 800w, /img/hero-1600.webp 1600w"
sizes="(max-width: 900px) 100vw, 1200px">
<img src="/img/hero-1600.jpg"
srcset="/img/hero-800.jpg 800w, /img/hero-1600.jpg 1600w"
sizes="(max-width: 900px) 100vw, 1200px"
alt="Hero"
width="1600"
height="800">
</picture>
El navegador elige el primer <source> cuyo type soporte. Navegadores con AVIF cogen el primero; con WebP (pero no AVIF), el segundo; el resto cae al <img>.
Repites sizes en cada source porque cada source tiene su propio srcset. Sí, es verboso. Un pipeline de build o un plugin de CMS suele generarlo.
Cuántas variantes necesitas
La respuesta práctica: menos de las que crees. Un conjunto habitual:
| Variante | Anchura | Caso de uso |
|---|---|---|
| Pequeña | 400-600 px | Móvil, miniatura |
| Mediana | 800-1000 px | Móvil retina, tablet, escritorio pequeño |
| Grande | 1400-1600 px | Escritorio, tablet retina |
| Extra grande | 2400 px | Escritorio retina, 4K |
Cuatro variantes cubren la gran mayoría de dispositivos reales. Subir a 6 u 8 raramente mueve el rendimiento real porque la selección del navegador redondea al siguiente candidato disponible.
Genéralas con Redimensionar imagen para casos puntuales, o con Redimensionar por lotes para un set completo. Para redes sociales concretas, Redimensionador para redes gestiona las dimensiones por plataforma.
No olvides width y height
Sale en cada artículo de imagen responsiva por buen motivo. En el tag <img>, width y height deben ser las dimensiones intrínsecas del archivo src. Los navegadores usan la proporción para reservar espacio antes de que la imagen cargue, que es lo que mantiene bajo el Cumulative Layout Shift.
<img src="/images/hero-800.jpg"
srcset="..."
sizes="..."
width="800"
height="400"
alt="...">
Puedes seguir estilando el tamaño con CSS (max-width: 100%; height: auto;). Los atributos son pistas de proporción, no tamaños fijos.
Lazy loading y fetch priority
Dos atributos que se combinan con srcset:
loading="lazy"difiere la carga hasta que la imagen esté cerca del viewport. Úsalo en imágenes bajo el pliegue. No lo uses en la imagen LCP — retrasa LCP.fetchpriority="high"dice al navegador que priorice esta imagen. Úsalo en la imagen LCP. No lo uses en todas — ponerlo en todo es lo mismo que no ponerlo en ninguna.
Plantilla típica:
<!-- Hero LCP -->
<img src="/hero-1600.webp" srcset="..." sizes="..."
width="1600" height="800"
fetchpriority="high" decoding="async"
alt="Hero">
<!-- Imagen bajo el pliegue -->
<img src="/body-800.webp" srcset="..." sizes="..."
width="800" height="600"
loading="lazy" decoding="async"
alt="Imagen cuerpo">
Un flujo realista
- Mantén originales tan grandes como vayas a necesitar (2400-3000 px de ancho para heros).
- En build time, genera variantes a 400w, 800w, 1600w, 2400w.
- Para cada variante, genera también WebP e idealmente AVIF.
- En tus plantillas, emite
<picture>con las tres fuentes de formato y elsizescorrecto para el layout. - Pon
width,heighty (en la imagen LCP)fetchpriority="high". - Usa
loading="lazy"ydecoding="async"en todo lo que esté bajo el pliegue.
Eso es todo. La recompensa es real: los sitios que adoptan este patrón suelen ver un 30-50% menos de ancho de banda de imagen en móvil, un Largest Contentful Paint visiblemente más rápido y un Cumulative Layout Shift tan bajo que Google deja de quejarse. Una vez hecho, el patrón se repite en cada página.
Herramientas relacionadas
Preguntas frecuentes
¿Cuál es la diferencia entre descriptores w y x en srcset?
Los descriptores w dicen al navegador la anchura intrínseca en píxeles de cada candidato (hero-800.jpg 800w significa que ese archivo tiene 800 px de ancho) y, combinados con sizes, el navegador elige la mejor opción para viewport y densidad de píxeles. Los descriptores x dicen la densidad para la que está pensado cada archivo (logo@2x.png 2x para Retina) y solo funcionan cuando la imagen se renderiza a un tamaño CSS fijo único. Usa w para imágenes que escalan con el viewport; usa x para logos, iconos y elementos UI de tamaño fijo. No puedes mezclar ambos en el mismo srcset.
¿Funciona srcset sin sizes?
Técnicamente sí, pero el navegador cae a asumir sizes=100vw — que la imagen llena todo el viewport — y descargará el archivo más grande de tu srcset. Normalmente no es lo que quieres. Si tu layout limita la imagen a 800 px, dilo en sizes (sizes=800px) para que el navegador pueda elegir un archivo más pequeño en pantallas estrechas. Olvidar sizes es una de las razones más comunes por las que srcset no ahorra ancho de banda en la práctica.
¿Necesito srcset si uso AVIF?
Sí. srcset gestiona variantes de tamaño; picture con type gestiona variantes de formato. Una imagen responsive moderna típicamente usa ambas: sources picture para AVIF y WebP, cada una con su propio srcset de variantes de anchura, y un img fallback con un srcset JPG. AVIF por sí solo te da eficiencia de formato pero sigues mandando un único tamaño a todos, desperdiciando ancho de banda en móviles y poniendo borroso en 4K. Combina las dos para la victoria completa.
¿Cuántas variantes de srcset necesito en realidad?
Menos de las que crees — cuatro anchuras (400w, 800w, 1600w, 2400w) cubren la gran mayoría de dispositivos reales. Subir a seis u ocho raramente mueve el rendimiento real porque la selección del navegador redondea al siguiente candidato disponible. Para contextos sociales o miniaturas, pueden sobrar menos variantes. Mantén el set pequeño, genéralo en build time con una herramienta de redimensionado, y emite markup picture con AVIF más WebP más un fallback JPG para cada variante.
¿Debería poner loading=lazy y fetchpriority=high en la misma imagen?
No — son opuestos. loading=lazy difiere la carga hasta que la imagen se acerca al viewport, lo cual quieres en imágenes bajo el pliegue pero es desastroso en la imagen LCP. fetchpriority=high dice al navegador que priorice la imagen, lo cual quieres en la imagen LCP. Usa fetchpriority=high más decoding=async en el hero y loading=lazy más decoding=async en todo lo que esté bajo el pliegue. Nunca pongas fetchpriority=high en todo — anula su propósito.