The blog of Juan Pablo Scaletti
¿Has hecho un sitio responsive pero sigues usando las mismas imágenes enormes al mostrarlo en un teléfono? Por suerte existe <code>srcset</code> para evitar eso fácilmente.

srcset: Imágenes responsive sin JavaScript

Maquetar una web responsive es muy común ahora. De hecho, desde que en el 2010 Ethan Marcotte publicó un artículo inventando el término “diseño web responsive” y –solo trece días más tarde– Steve Jobs anunció las pantallas “retina”, las webs e imágenes de ancho fijo se han vuelto prácticamente obsoletas.

Ahora también son comunes las cabeceras con imágenes enormes que se ven gloriosas en laptops con pantallas retina (y que cargan en un segundo en una conexion a Internet mejor que la mia).

El problema es que es el mismo sitio que alguien intenta ver con un teléfono en 3G: ese mega de imágenes no solo hace la carga más lenta, si no que además es por las puras, por que las imágenes se ven mucho más pequeñas en su pantalla.

Podrías desarrollar tu propia solución con JavaScript para que calcule cual imágenes cargar… pero este artículo se trata de una solución nativa soportada en (casi) todos los navegadores (y hay formas de hacerlo funcionar en los demás). Se llama srcset y se usa así:

<figure class="fullwidth">
    <img
        srcset="
            beach-320.jpg 320w,
            beach-640.jpg 640w,
            beach-700.jpg 700w,
            beach-800.jpg 800w,
            beach-1024.jpg 1024w,
            beach-1280.jpg 1280w,
            beach-1560.jpg 1560w,
            beach-1840.jpg 1840w"
        alt="Responsive Beach">
</figure>

¿Que es esta lista de imágenes? Es jústamente, ¡un conjunto (un “set”) de src!

Son las imágenes que que tienes disponibles y sus anchos. Podrían ser solo dos o cuantas prefieras. Con esa información, el navegador puede elegir automáticamente que imagen cargar en cada situación.

Abajo puedes ver la imagen. Para ver el efecto funcionar, cambia el ancho de esta ventana.

NOTA: En Chrome o Safari no funciona. No por que no soporten srcset, si no por que Chrome utiliza siempre la imágen más grande que tenga en caché, aunque sea overkill, y Safari solo hace el cálculo al cargar la página. Así que para ver cambiar el src de esta imagen “al vuelo”, cuando cambias el ancho de la ventana, tendrás que usar Firefox. ¿Consistencia? ¡¿Quien la necesita?! ;)

Responsive Beach

No importa si tu pantalla es un teléfono en blanco y negro o un monitor Ultra HD, el navegador hará todos los cálculos necesarios y decargará la imagen más pequeña que se vea bien. El mismo código servirá para todos los tamaños de pantalla y densidad de pixeles, así que no tienes por que decidir que pantallas pequeñas deberías soportar. ¡Todas están cubiertas! Incluso si el próximo años lanzan un smartwatch de 4.678x.

Es más, les deja a los navegadores espacio para que en el futuro puedan hacer otras optimizaciones como, no se, usar un tamaño menor si el Internet está muy lento, si el usuario dice que lo prefiere o cosas así.

Por lo mismo, el srcset está pensado para una misma imagen en diferentes tamaños. No hay garantía de cuando un navegador muestre una imagen u otra. Por ejemplo, ya mencioné que Chrome, cuando pueda, usará la imagen más grande que ya haya descargado.

srcset es para tener una misma imagen en diferentes tamaños, no para tener diferentes imágenes para cada tamaño/densidad de pixeles
srcset es para tener una misma imagen en diferentes tamaños, no para tener diferentes imágenes para cada tamaño/densidad de pixeles

Si realmente necesitas hacer “dirección de arte” y controlar con exactitud que imagen se muestra en cada resolución y densidad de pantalla, srcset no te servirá, lo que buscas es la también nueva etiqueta , pero el control viene al precio de tener que hacer todos los cálculos de tamaño/resolución a mano para cada imagen.

El atributo sizes

Hay un problema con el srcset: tu navegador empieza a cargar la imágen antes de haber procesado los estilos o el javascript, entonces, no sabe cual es el tamaño de su contenedor. Lo único que conoce es el ancho total de la ventana, asi que supone que la imagen tiene que ocupar todo el ancho para hacer los cálculos.

Esto significa aunque la imagen aparezca en una columna estrecha, si la ventana del navegador es grande, este cargará siempre la imagen más grande que encuentre. ¡Que desperdicio!

Es como funcionan los navegadores, no hay mucho que hacer… excepto decirle al navegador exactamente cuanto del ancho de la ventana realmente va a usar la imagen. Para eso, junto con srcset puedes usar el atributo sizes, de esta forma:

<figure>
    <img
        sizes="(min-width: 576px) 50vw, 100vw"
        srcset="
            beach-320.jpg 320w,
            beach-640.jpg 640w,
            beach-700.jpg 700w,
            beach-800.jpg 800w,
            beach-1024.jpg 1024w,
            beach-1280.jpg 1280w,
            beach-1560.jpg 1560w,
            beach-1840.jpg 1840w"
        alt="Responsive Beach">
</figure>

Como ves, se usa una sintaxis igual a la de las media queries. En este código de ejemplo le estoy diciendo que, cuando el ancho de la ventana sea más que 576 pixeles, la imagen máximo ocupará la mitad de ese ancho, pero cuando sea más estrecha si ocupará todo el ancho. Es un caso típico en que muestras dos columnas en escritorio pero solo una en un teléfono.

En realidad no me gusta mucho esta solución, detesto que haya que definir estos media queries (para pasar dos a una columna) dos veces, primero en el CSS y luego en el HTML, pero es lo mejor que hay por ahora :(.

Los sizes pueden especificarse como porcentajes del ancho total, con vw, como el ejemplo de arriba; o con pixeles, cuando pasa que, como en este blog, tienes un contenedor con un ancho máximo centrado con margin: 0 auto.

<figure>
    <img
        sizes="(min-width: 576px) 370px, 100vw"
        srcset="
                ...

Si estás leyendo este artículo en un teléfono, las dos imágenes de abajo se verán iguales. Pero la de la derecha no tiene sizes, así que en pantallas más grandes puedes ver como carga imágenes innecesariamente grandes para el tamaño que ocupa.

Responsive Beach
Responsive Beach

Como ya dije más arriba, Chrome muestra la imagen más grande que tenga en caché, así que en este navegador las dos imágenes van a verse iguales, pero puedes comprobarlo usando Firefox o Safari.

¿Internet Explorer?

Internet Explorer es el único navegdor importante que no soporta nativamente el atributo srcset en ninguna de sus versiones. ¡Ni siquiera en Edge! (Ahora si viene en Edge)

Pero no hay problema por que existe un excelente polyfill que simula de forma perfecta el soporte a srcset y sizes: PictureFill. No hay nada que configurar, solo incluirlo al principo de tu página y te olvidas:

<script>document.createElement("picture"); //HTML5 shiv </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/picturefill/3.0.3/picturefill.min.js" async></script>

Aunque pesa solo 13KB, puedes hacer que no cargue si es que no lo necesitas, usando este código, en vez del de arriba:

<script>
 if (! 'sizes' in window.document.createElement('img')){
  document.createElement('picture');
  var script = window.document.createElement('script');
  script.src = 'https://cdnjs.cloudflare.com/ajax/libs/picturefill/3.0.3/picturefill.min.js';
  script.async = true;
  script.insertBefore(window.document.getElementsByTagName('script')[0]);
}
</script>

(Probamos si soporta sizes en vez de srcset por que versiones antiguas de Safari implementan una versión antigua de srcset que no tiene sizes).

Imagenes de fondo

Lamentáblemente todo esto solo aplica a imágenes en el contenido, no a imágenes de fondo puestas por CSS. Pero te propongo tres alternativas:

  1. Simular que una <img> es un background-image usándo position: absolute. Eso es lo que hago en la cabecera de este artículo. Puedes simular los background-size jugando con transformaciones, anchos y altos mínimos.
.content-header__image img {
   min-height: 100%;
   min-width: 100%;
   position: absolute;
   left: 50%;
   top: 50%;
   transform: translateY(-50%) translateX(-50%);
}
  1. Usar @media-query para cargar diferentes imágenes. El único problema es que tienes que hacer los cálculos para cada tamaño de imagen y densidad de pixeles.

  2. ¡JavaScript! Este otro sitio: https://www.stitcher.io/blog/responsive-images-as-css-background propone tener una imagen con srcset oculta y con JavaScript leer el src que el navegador le ponga y usarlo como imagen de fondo de su contenedor. No he probado su código, pero parece que podría funcionar.

Conclusión

¿Todo es ahora hermoso, el sol brilla, los pajaritos cantan y un arcoiris cruza el cielo? Casi; srcset tiene dos inconvenientes:

¿Son problemas fatales? N lo creo. srcset, hace lo que promete, no necesita de JavaScript para funcionar –excepto en IE/Edge, donde nadie nunca lo desactiva– y no tienes que programar nada ni corregir bugs o aprender una nueva sintáxis. Me gustaría que la implementación, sobretodo la de los sizes, fuera un poco diferente, pero de hecho lo estoy usandp para todas las imágenes en este blog.

¿Comentarios? ¿Dudas? ¡Escríbeme!