Guía práctica de imágenes responsive (I)


imagenes_responsive

Tras mucho tiempo a vueltas con la especificación, todo apunta a que 2015 será el año de las imágenes responsive.

Los principales navegadores comienzan a dar soporte y el último en unirse a la fiesta ha sido Mozilla con su Firefox 38, que ya es compatible con las técnicas.

Solo quedan, Safari que solo lo soporta parcialmente e Internet Explorer que está en ello… mientras tanto tenemos Picturefill.

El problema

Actualmente, muchos de los sitios web que aplican técnicas responsive, están usando los mismos recursos para todos los dispositivos.

Para el caso de las imágenes y siguiendo las directrices del diseño web responsive (fluid images), estos sitios se limitan a mostrar la imagen de mayor tamaño y aplican un max-width:100% para dejar que el navegador “adapte”, esa misma imagen, al espacio disponible en anchura de cada dispositivo.

img{
    max-width:100%;
   }

En determinados proyectos, esto supone un problema grave de rendimiento, sobre todo en usuarios de dispositivos móviles, ya que aumenta innecesariamente el tamaño de página y el tiempo de carga.

Además del problema de rendimiento, tenemos que pensar que si una imagen para pantallas de escritorio, cuyo peso sea de 600Kb, es cargada por un dispositivo móvil que no esté usando una conexión Wi-Fi, son 600Kb menos disponibles en la tarifa de datos del usuario ese mes!

La solución: imágenes responsive

Al usar imágenes responsive lo que hacemos es “utilizar diferentes archivos de imagen con tamaños apropiados para los distintos dispositivos”. De esta manera nos aseguramos que cada usuario recibe el contenido más adecuado a las caracteristicas de su pantalla y velocidad de conexión.

Existen muchos escenarios, pero hay dos que predominan sobre el resto: el cambio de resolución (resolution switching) y la dirección de arte (art direction).

Cada escenario utiliza una técnica diferente: el atributo srcset para el cambio de resolución y el elemento picture para la dirección de arte. Estas técnicas no están enfrentadas y pueden coexistir a la vez en una misma página, lo importante es usarlas en el contexto adecuado.

La estrategia a la hora de usar una u otra opción será distinta dependiendo de los objetivos y el tipo de proyecto.

En esta primera parte de la guía vamos a centrarnos en la primera de ellas: el uso del atributo srcset.

Atributo srcset

Utilizaremos el atributo srcset del elemento img cuando queramos usar diferentes tamaños de imagen, pero sin modificar su ratio (aspect ratio), es decir, sus proporciones.

Con srcset, nosotros le proporcionamos al navegador un listado de imágenes, pero corresponde a este elegir qué imagen se carga en cada momento dependiendo del valor de la descripción.

El atributo srcset puede trabajar de dos maneras:

1.- Descripción de la densidad de pixel (pixel density description)

<img 
    srcset="http://satyr.io/300x150 1x,
            http://satyr.io/450x225 1.5x, 
            http://satyr.io/600x300 2x, 
            http://satyr.io/1200x600 3x"
    src="http://satyr.io/300x150"
    alt="Item #01" />

scrset_pixeldensitydescriptor

Esta forma de usar srcset está orientada a dispositivos con pantalla retina y nos permite mostrar una imagen acorde a su densidad de píxeles. En este caso, las imágenes son estáticas, no cambian su tamaño y siempre es el mismo, por lo que es conveniente fijar sus valores de anchura y altura vía CSS.

En el valor de srcset incluiremos la ruta de la imagen, un espacio en blanco y su densidad de píxel (1x, 1.5x, 2x, 3x,…). Si necesitamos incluir más de una imagen, las iremos separando con comas.

Para evitar problemas con los navegadores que no reconozcan srcset, mantendremos el clásico atributo src a modo de fallback con el tamaño en anchura más pequeño de la imagen.

Ver demo en Codepen

En este ejemplo, el navegador elige la imagen en función de la densidad de píxels de la pantalla del dispositivo (device pixel ratio). Para calcular ese valor, divide el tamaño del viewport actual entre el valor de anchura de la imagen. Supongamos que queremos mostrar una imagen de 300x150px y tenemos un viewport de 320px, los valores serían:

300/320 = 0.9375

450/320 = 1.4062

600/320 = 1.875

1200/414* = 2.8985

Segun estos calculos, en pantallas desktop no retina 1x, el valor más cercano a 1 es 0.93 por lo que veremos las imágenes de 300x150px, si accediesemos desde un Nokia Lumia 520 que tiene una densidad de 1.5x , se verán las de 450x225px por su cercanía a 1.40, pero si lo hacemos desde un dispositivo con pantalla retina 2x , el tamaño será 600x300px y en el caso de acceder desde un iPhone 6 Plus 3x (su anchura mínima es 414px), se cargarían las imágenes de 1200×600 por la proximidad del valor 2.89 a 3.

Esta forma de trabajar con srcset es la más compatible con navegadores, ya que hasta Safari e iOS Safari lo soportan… pero es la menos usada.

2.- Descripción de anchura (width description)

<img 
    srcset="
        http://satyr.io/480x240 480w,
        http://satyr.io/700x350 700w,
        http://satyr.io/900x450 900w" 
    src="http://satyr.io/480x240" 
    alt="Products" />

En esta ocasión, seguimos proporcionando la ruta de las distintas imágenes con diferentes tamaños, pero ahora incluimos a modo de descripción, la anchura en pixels de cada una de ellas (480w, 700w, 900w, …), separando ruta y descripción con un espacio en blanco.

A diferencia del caso anterior, las imágenes son flexibles, ocupando todo el espacio disponible dentro de su contenedor y es siempre el navegador el que elige que imagen se carga. Pero, ¿cómo decide cuál de ellas debe hacerlo?

El navegador, cuando “parsea” el HTML y comienza a descargar las imágenes de una página, todavía no dispone ni del CSS, ni del JS, por lo que no sabe cuanto espacio tiene que dejar disponible para cada una de ellas.

Para ayudarle a conocer esa información tenemos que usar un nuevo atributo: sizes.

<img 
    srcset="
        http://satyr.io/480x240 480w,
        http://satyr.io/700x350 700w,
        http://satyr.io/900x450 900w,
        http://satyr.io/1200x600 1200w"     
    sizes="100vw"     
    src="http://satyr.io/480x240"     
    alt="Products"> 

Con sizes, lo que hacemos es proporcionar un valor de anchura para la imagen en relación al viewport, permitiendo que el navegador elija y descargue la que considere más adecuada de las dispuestas en srcset.

La sintaxis de sizes incluye un tamaño para la imagen pero tambien es posible añadir una o varias condiciones (media condition) muy parecidas a las que usamos en las mediaqueries, junto al tamaño que debería tener la imagen para cada una de ellas. El valor predeterminado de sizes es 100vw, que corresponde al 100% de la anchura del viewport.

Ver demo en Codepen

Elegir correctamente los valores del atributo sizes puede ser complicado. Vamos a suponer un valor igual a 100vw y que accedemos desde un dispositivo iPad con pantalla retina.

Podríamos usar la misma operación que en el caso anterior para conocer cuál de las imágenes se cargará dependiendo del tamaño del viewport. Pero para la descripción de anchura, prefiero utilizar otro método que resulta muy útil para “intuir” la imagen que se verá en cada momento.

El proceso consiste en averiguar primero el tamaño actual del viewport en pixeles y multiplicarlo por el pixel-ratio de dispositivo. Para conocer estos valores, lo ideal es conectarse a MQtest.io desde el propio dispositivo.

Tambien podemos visitar screensiz.es para consultar los tamaños de viewport y el pixel-ratio de la mayoría de dispositivos.

Siguiendo con nuestro ejemplo, desde un iPad(portrait) con pantalla retina tendríamos 768px (viewport) * 2 (pixel-ratio) = 1536px.

mqtest

A continuación, listamos todos los tamaños de imágenes del srcset y vamos a establecer rangos de la siguiente manera:

viewport * pixel-ratioImagen probable que se cargará
Hasta 480px480px
Desde 481px hasta 700px700px
Desde 701px hasta 900px900px
Desde 901px hasta 1200px1200px
Desde 1201px en adelante1200px

El criterio del navegador tiende a “tirar hacia arriba”, cuando el valor (viewport*pixel-ratio) sea de 480px, cargará la de 480w, pero a partir de 481px buscará la siguiente en tamaño, en este caso la de 700w.

En nuestro caso, como teniamos como resultado 1536px, la imagen que se cargará será la de 1200px. Si el dispositivo fuese un iPad sin pantalla retina con el mismo viewport de 768px, la imagen sería la de 900px.

Esta forma de “calcular” la imagen que posiblemente elegirá el navegador, nos facilita mucho la tarea de rellenar el valor de sizes. Vamos a ver otros ejemplos, un poco más complejos.

<img 
    srcset="
        http://satyr.io/480x240 480w,
        http://satyr.io/700x350 700w,
        http://satyr.io/900x450 900w,
        http://satyr.io/1200x600 1200w"     
    sizes="(min-width:64em) 30em, 100vw"
    src="http://satyr.io/480x240"
    alt="Products"> 

Aquí le estariamos diciendo al navegador que cuando el viewport sea mayor de 64em, la imagen tiene que medir 30em y para el resto de casos (menos de 64em), la imagen utilizará una anchura que ocupe todo el viewport (100vw).

Suponiendo 1em = 16px y aplicando nuestros cálculos, en una pantalla no retina a partir de una anchura de viewport de 1024px (64em*16), la imagen que se cargará será la de 480px (30em*16).

Si la pantalla es retina 2x, tendríamos 2048px (64em*2*16) y un espacio para la imagen de 960px (30em*2*16) por lo que la imagen que se cargará será la de 900px.

Si en vez de valores fijos, queremos que las imágenes sean flexibles y crezcan o disminuyan su anchura en función del viewport, podemos usar unidades vw:

<img 
    srcset="
        http://satyr.io/480x240 480w,
        http://satyr.io/700x350 700w,
        http://satyr.io/900x450 900w,
        http://satyr.io/1200x600 1200w"     
    sizes="(min-width:30em) 50vw, (min-width:64em) 33vw, 100vw"
    src="http://satyr.io/480x240"
    alt="Products"> 

En este ejemplo, cuando el viewport sea mayor de 30em, la imagen tiene que medir el 50% del viewport. Para un tamaño entre 30em y 64em, medirá el 33% de este y para el resto de casos (menos de 30em), el 100% del viewport.

Por último, resaltar dos detalles con respecto a sizes:

  • El uso de sizes es obligatorio en el caso de descripciones de anchura pero no es válido cuando usamos descripciones de densidad de píxel.
  • La compatibilidad tiene que mejorar, hasta la fecha solo reconocen el atributo las últimas versiones de Chrome, Firefox, Opera y navegadores Android.

Picturefill

Para aquellos navegadores que todavía no soportan nativamente las imagenes responsive, tenemos que usar un “polyfill” llamado PictureFill.

<script src="picturefill.js" async></script>

Es recomendable usar el atributo async, para evitar bloquear la carga de la página.

Si usamos PictureFill, hay que quitar el atributo src que usabamos como fallback, para evitar que los navegadores no compatibles carguen la imagen por duplicado.

<img 
    srcset="
        http://satyr.io/480x240 480w,
        http://satyr.io/700x350 700w,
        http://satyr.io/900x450 900w,
        http://satyr.io/1200x600 1200w"     
    sizes="100vw"          
    alt="Products"> 

Resumen de srcset

Cuando vayamos a usar imagenes responsive y necesitemos mantener las proporciones de las imágenes, utilizaremos el atributo srcset.

Si incluimos la descripcion de densidad de pixel, será para imagenes fijas y orientadas a que se vean bien en dispositivos con pantalla retina.

Si incluimos la descripción de anchura, será para imagenes flexibles y estaremos obligados a añadir el atributo sizes para que el navegador sepa cual es el tamaño de imagen que se necesita en relación al viewport.

En ambos casos, con srcset siempre elige el navegador!

Hasta aquí esta primera parte, en un proximo artículo veremos como trabajar con el elemento picture y el otro escenario: la dirección de arte.