Introducción

Imágenes, recurso imprescindible para la Web

Las imágenes en la Web son elementos imprescindibles a la vez que suponen un importante consumo de recursos. Si queremos que un sitio se adapte a los dispositivos tanto en tamaños de pantalla como en optimización de la velocidad de carga, tenemos que gestionar las imágenes con HTML-5.1. Las mejoras incorporadas son los nuevos atributos srcset y sizes para el elemento <img> así como un nuevo elemento <picture>.

He seguido la especificación WHATWG HTML Embedded content desarrollando algunos ejemplos que se exponen ahí como ilustrativos. La idea básica es que tenemos que entender los casos de uso que vamos a aplicar a una imagen para entonces implementar alguna de las técnicas de la especificación. Se trata de realizar una selección de imagen con distintos formatos y/o tamaños según varios factores:

  1. En este tema veremos una selección basada en DPR, de tal forma que el navegador seleccionará una imagen de una lista que mejor se adapte a la resolución del dispositivo. Para ello usaremos el nuevo atributo srcset con descriptor nx para el elemento <img>, donde n>0 es un número real.
  2. Otra posibilidad es realizar una selección basada en el viewport también con el elemento <img> y su atributo srcset pero con descriptor nw, donde n>0 es un número entero, y también otro nuevo atributo sizes con una lista de tamaños de origen.
  3. El atributo sizes del elemento <img> permite también especificar una consulta de medios para la selección basada en viewport. Esto ayudará a seleccionar la imagen según el ancho de pantalla del dispositivo.
  4. Otro caso de uso es la selección basada en dirección artística, donde es preferible usar el nuevo elemento <picture>. Con este elemento también podemos hacer una selección basada en formato de imagen. Aprovechamos este caso de uso para exponer el nuevo formato de imagen WEBP desarrollado por Google.

Estos cuatro temas presentan cada caso como un problema a resolver y la forma de solucionarlo usando las nuevas técnicas de gestión de imágenes de HTML-5.1.

Estos nuevos atributos y elementos aún no son ampliamente soportados. En Caniuse puede consultar estos soportes. Con las versiones actuales la situación es esta:

MejoraChrome 41Opera 28Firefox 36IE 11Safari 8
img srcset sizesCon flagNoParcial
picture sourceCon flagNoNo

En Firefox puede activarse about:config preference dom.image.srcset.enabled y también para picture. He probado los ejemplos en los navegadores anteriores a excepción de Safari.

El nivel de soporte no es el ideal para implementar estas nuevas técnicas y es posible que cambien algo en la especificación. Pero creo que debemos empezar a hacer pruebas con lo nuevo cuando ya hay una perspectiva de que va a consolidarse. No olvidemos que HTML-5 pasó a REC (Recommendation como estándar) recientemente en el 2014. Y que al mismo tiempo también iniciaron la nueva versión HTML-5.1 (WD Working Draft), con lo que aún tendrá que pasar un largo tiempo hasta que se consoliden (o no) todas sus mejoras. Pero no podemos esperar a que finalice HTML-5.1 para ir conociendo lo nuevo, porque entonces serán muchos los cambios y demasiado tarde para aprenderlos.

Aclarando el concepto Device Pixel Ratio (DPR)

Antes de meternos en materia recordaremos algunos conceptos que expuse en el tema sobre adaptación de dispositivos. El término device pixel ratio (DPR) viene a ser la relación entre píxeles físicos y píxeles CSS. Los primeros son los que realmente tiene el dispositivo, mientras que los segundos son los que dispone el autor de la página para presentar los contenidos. Términos equivalentes al DPR son densidad de píxel de pantalla o incluso resolución en este contexto.

Así un dispositivo como el IPhone 5 tiene un viewport visual de 640×1136 píxeles de dispositivo (o píxeles físicos) y 320×568 píxeles CSS en orientación portrait. Por lo tanto hay una relación 2×1 y podemos abreviar diciendo que es un dispositivo con una resolución o DPR 2x. Si en una página tenemos declarado un elemento de 200px de ancho, estos serán dibujados por el navegador del IPhone 5 usando 400px porque tiene una resolución 2x. Con JavaScript podemos obtener ese dato para el dispositivo con el que esté visualizando esta página, tal como vemos en este ejemplo:

Ejemplo: Device Pixel Ratio para este dispositivo

Este dispositivo tiene un Device Pixel Ratio (window.devicePixelRatio) de:
?
píxeles físicos / píxel CSS.

El autor de las páginas entonces se olvida de los píxeles físicos y se centra sólo en los píxeles CSS, los de toda la vida. Porque será el navegador el encargado de traducirlos. Pero hay algunos casos como la presentación de imágenes donde el autor si ha de tener en cuenta los efectos de la resolución.

El problema de la pérdida de calidad de imágenes en dispositivos de alta resolución

La mejor forma de insertar imágenes en un documento HTML es usando el elemento <img>. En su atributo src ponemos la URL y especificamos el ancho y alto original de la imagen para optimizar la velocidad de carga. El siguiente ejemplo contiene una imagen de 200×200 píxeles que he obtenido con el generador de fractales (un fractal de Newton z4-1).

Ejemplo: Insertando imágenes en un elemento <img>

Fractal Newton z4-1
Imagen de 200×200 píxeles en un elemento <img> declarado en su atributo src.

Mientras lo visualicemos con dispositivos de resolución simple donde un píxel de dispositivo equivale a uno CSS todo irá bien. Pero el problema aparece cuando reproducimos esa imagen en dispositivos de mayor resolución. Por ejemplo, el Samsung Galaxy S4 tiene 1080×1920 píxeles físicos que se convierten en 360×640 píxeles CSS en orientación portrait. Esto supone una relación de 3×1 lo que suele expresar como un dispositivo 3x.

Ejemplo: Pérdida de calidad en dispositivos de alta resolución 3x

Fractal Newton z4-1
La imagen anterior de 200×200 píxeles en un dispositivo de alta resolución 3x como el Samsung Galaxy S4 perderá calidad.

La pérdida de calidad es porque el dispositivo de alta resolución tiene que trasladar los píxeles de una imagen de 200×200 a una área de 600×600 píxeles. La imagen del ejemplo anterior es parte de una captura de pantalla del Samsung Galaxy S4 de 1920×1080 píxeles físicos en orientación landscape, recortando la imagen y reduciéndola 3 veces, lo contrario a lo que hace el dispositivo para presentarla en 600×600 píxeles físicos partiendo de una imagen de 200×200 píxeles.

La solución propuesta pasa por agregarle al elemento <img> un nuevo atributo srcset donde relacionamos otros ejemplares de la imagen con distintos DPR. Para observar este ejemplo conviene usar un dispositivo 3x con objeto de comparar la calidad de las imágenes.

Ejemplo: Usando atributo srcset

Fractal Newton z4-1, imagen sin atributo srcsetFractal Newton z4-1, imagen con atributo srcset
Usando el atributo srcset para que el navegador descargue la imagen apropiada. Si observa este ejemplo en un dispositivo 3x verá que la primera imagen es de peor calidad.
Usando JavaScript tras la carga obtenemos algunas propiedades de ambas imágenes:
PropiedadSin srcsetCon srcset
Medidas
(píxeles CSS)
width, height
??
Medidas originales
(píxeles físicos)
naturalWidth, naturalHeight
??
URL
currentSrc
??

La primera imagen está insertada como la del primer ejemplo, usando sólo el atributo src en el <img>. Para la segunda agregamos srcset con una lista de imágenes de mayor resolución. Este es el código del ejemplo anterior:

<img src="ejemplos/fractal-newton-200x200.png"
alt="Fractal Newton z4-1" width="200" height="200" />

<img src="ejemplos/fractal-newton-200x200.png"
srcset="ejemplos/fractal-newton-400x400.png 2x, 
        ejemplos/fractal-newton-600x600.png 3x"
alt="Fractal Newton z4-1" width="200" height="200" />

El segundo elemento <img> tiene el clásico atributo src donde pondremos la imagen con resolución 1x, pero sin agregar el descriptor a la URL. Así para aquellos navegadores que no soporten srcset tomarán la URL de src. Para los que si lo soporten entenderán que la URL que viene en src será siempre 1x. Luego en srcset ponemos una lista de imágenes de mayores resoluciones. En este caso una de 2x y otra de 3x.

Consideraciones con el atributo srcset con descriptor DPR

Aprovechar la alta resolución de los dispositivos móviles es una gran tentación. Especialmente cuando presentamos fotografías. Pero la técnica del srcset nos obliga a disponer de un juego en varias resoluciones de la misma imagen. Además las de mayor resolución también serán las de mayor tamaño:

ResoluciónArchivoBytesTamaño
relativo
1xfractal-newton-200x200.png118901
2xfractal-newton-400x400.png377653.18
3xfractal-newton-600x600.png728726.13

Por otro lado el tamaño es proporcional al cuadrado de la resolución. Es decir, una imagen de 200×200 tiene 40000 píxeles, pero una de 400×400 tendrá 160000, es decir, 4 veces más píxeles. Mientras que una de 600×600 tendrá 360000 con 9 veces más píxeles. Con la compresión PNG y optimizándolas conseguimos bajar esa proporcionalidad, pero aún así la imagen 3x tiene un tamaño de archivo de más de 6 veces que la de 1x.

Y para complicar más las cosas resulta que la alta resolución se produce en dispositivos móviles donde la velocidad de la red es un factor determinante. Efectos como el de la latencia web puede dar al traste con el objetivo de conseguir imágenes de gran resolución a costa de una lenta presentación. Para un archivo de 72872 bytes necesitaremos entre 5 y 6 RTT's con la ventana inicial (IW) de TCP establecida en 14600 bytes.

Factores de zoom y red para la selección basada en DPR

En un ejemplo de uso del atributo srcset en combinación con el descriptor DPR, la especificación HTML5.1 Embedded content expone un ejemplo de selección basada en DPR cuando el tamaño renderizado de la imagen es fijo. No es otra cosa que lo que hemos estado viendo en este tema. En nuestros ejemplos la imagen final tiene un tamaño fijo de 200 píxeles CSS, pero con srcset se selecciona un recurso que se adapta al DPR del dispositivo. Concretamente esa especificación dice:

El navegador puede elegir alguno de los recursos dados dependiendo de la densidad de píxel de pantalla (DPR), nivel de zoom y posiblemente otros factores tal como las condiciones de la red.

Pero realmente no he podido probar que el navegador tenga en cuenta estos dos últimos factores. En el emulador que viene con el Developer Tools de Chrome 40 podemos seleccionar un dispositivo 3x así como una conexión lenta de 50 Kbps:

Emulador del Developer Tools del navegador Chrome

Vemos que sólo atendió a la resolución porque las medidas originales de la imagen naturalWidth y naturalHeight son de 600×600 (también se comprueba buscando la propiedad currentSrc exponiendo la URL al archivo "fractal-newton-600x600.png"). Descargar 72872 bytes que pesa esa imagen en una red lenta supone una merma en la presentación.

También dice la especificación que el navegador podría solicitar la imagen con resolución más apropiada para el nivel de zoom inicial de la página. Pero he probado esto en mi Samsung Galaxy S4 con DPR 3x cambiando la adaptación de viewport inicial 1 a un valor más pequeño de 0.5, quedando <meta name="viewport" content="width=device-width, initial-scale=0.5" />. He tomado algunas capturas de pantalla de ese móvil con ambos niveles de zoom para ver los efectos.

Móvil con DPR 3x
Página con zoom 100% en móvil DPR 3xImagen no disponible
Captura de pantalla de un móvil DPR 3x con un initial-scale=1 (100% zoom)

En la primera captura de pantalla vemos como se muestra el último ejemplo con initial-scale = 1, es decir, un zoom del 100%. Las imágenes, que siempre tiene un tamaño de 200 píxeles CSS, con este zoom ocuparán 200 × 3 DPR = 600 píxeles físicos. Pero si ponemos un zoom inicial de 50% las imágenes ocuparán la mitad, 300 píxeles físicos (ver segunda captura).

Así que para la segunda imagen que se suministra con el atributo srcset, el navegador tendría que haber elegido la versión fractal-newton-400x400.png pues con esa tendría píxeles suficientes, incluso aunque esta opción tuviera el descriptor 2x inferior al DPR del dispositivo. Sin embargo y tal como se observa en la última captura de pantalla, aún así descargó la versión de 600×600 píxeles.