Recordando algunos conceptos relacionados con el viewport

En el tema sobre adaptación de dispositivos ya expuse que es el bloque de contención inicial y el viewport. Podemos asimilarlo con la ventana del navegador, pero conviene leer ese tema si no tiene clara esa materia.

Para la ejecución de los ejemplos en esta página necesitamos saber más sobre los nuevos valores de longitudes en porcentajes del viewport de HTML5. Son valores relativos al bloque de contención inicial y por tanto orientados al diseño adaptativo. Veámos como son:

  • nvw siendo n un número de porcentaje de viewport. La letra v indica viewport y la letra w indica width, ancho. Por ejemplo, si el viewport es de 360px y aplicamos a un elemento el estilo width:10vw, le estamos diciendo que su ancho será de un 10% del ancho del viewport. Así tendremos un ancho para el elemento de 10 × 360 / 100 = 36px.
  • nvh lo mismo para el alto (h por height).
  • nvmin indicando min el más pequeño de los valores ancho y alto del viewport.
  • nvmax indicando max el mayor de los valores ancho y alto del viewport.

El problema del banner responsive

El banner a modo de imagen que ocupa todo el ancho del viewport y que suele ubicarse al inicio de la página es un recurso muy utilizado. Hace tiempo hice un par de pruebas de cabeceras elásticas con banner usando una imagen JPG de 26.4 KB con medidas 1000×100 píxeles. La imagen original era PNG pero la pasé a JPG con calidad inferior para que pesara menos. Un ejemplo usaba la propiedad background de CSS. En el otro ejemplo había un elemento <img> sin declaración de medidas width y height, con la finalidad de que la imagen se ajustará al ancho de la ventana establecido con width: 100% en el CSS.

Para que funcionara adecuadamente necesitábamos una imagen grande para cuando el viewport fuera más pequeño se ajustara sin pérdida de calidad. O también para el caso de dispositivos de alta resolución, con objeto de disponer de píxeles suficientes para no perder calidad. Hay algunas consideraciones a tener en cuenta con los ejemplos anteriores:

  • Si una imagen no tiene ningún significado semántico debería ser insertada con CSS. En otro caso habría que usar un elemento <img> en combinación con su atributo alt de texto alternativo. Así, por ejemplo, los buscadores las indexarán. Un banner que define el sitio puede tener un importante significado semántico.
  • Para la optimización de la velocidad de carga es necesario establecer los atributos width y height del elemento <img>. En otro caso el navegador tendrá que hacer un repintado de página al recibir la descarga de la imagen y conocer entonces sus medidas.

Ahora aprovechando el nuevo uso de longitudes de porcentajes del viewport comentado en el apartado anterior, realizo un nuevo enfoque para un banner con una imagen de 1280×146 pixeles y un tamaño de 56088 bytes. Este ejemplo usa un elemento <img> con el clásico atributo src declarando además las medidas iniciales width="1280" height="146":

Ejemplo: Usando <img> clásico para un banner

Lorem ipsum ad his scripta blandit partiendo, eum fastidii accumsan euripidis in, eum liber hendrerit an. Qui ut wisi vocibus suscipiantur, quo dicit ridens inciderint id. Quo mundi lobortis reformidans eu, legimus senserit definiebas an eos. Eu sit tincidunt incorrupte definitionem, vis mutat affert percipit cu, eirmod consectetuer signiferumque eu per. In usu latine equidem dolores.

Código completo de este ejemplo

Básicamente hay un <div class="banner"> que contiene un <div class="imagen-banner"> y otro <div class="titulo-banner">. La imagen se posiciona naturalmente en la parte superior izquierda de su contenedor exterior. El título lo posicionamos de forma absoluta para que ocupe el punto (0, 0), quedando encima de la imagen.

Al título le damos CSS .titulo-banner {... font-size: 2.4em; font-size: 6vw; ...} para establecer el tamaño de la fuente a un 6% (6vw) del ancho del viewport. La propiedad se sobrescribe con objeto de que los navegadores que no soporten el nuevo valor de longitud vw se queden con el valor en em.

Para adaptar la imagen establecemos sus medidas .imagen-banner img {width: 100vw; height: 11.4vw;}. Con esto tomará el 100% del ancho del viewport. El alto será de un 11.4% puesto que la relación alto/ancho de la imagen es de 146/1280 = 0.114. Para navegadores que no soporten esto establecemos previamente las medidas en porcentajes del contenedor.

El ejemplo funciona pero no es totalmente responsive, es decir, no es adaptativo desde el punto de vista del uso eficiente de recursos. Por ejemplo, un dispositivo de 320 píxeles de ancho de viewport y 1 DPR no necesita una imagen de 1280 píxeles y 56088 bytes. Con una de 320 píxeles que sería de un tamaño en KB significativamente inferior le hubiese bastado. Y en redes de conexión móvil que son más lentas es un factor muy importante. Este es un problema que intenta resolver los nuevos atributos sizes y srcset con descriptor ancho de imagen (w) para el elemento <img>.

Selección basada en el Viewport con distintos tamaños de imágenes (<img> con srcset y sizes)

Para implementar un banner que se adapte al viewport partimos de una imagen original de 1750×200 píxeles, de la que generamos unos ejemplares con dimensiones menores y manteniendo la relación de aspecto ancho/alto:

ImagenBytes
banner-1750x200.png96055
banner-1280x146.png56088
banner-1000x114.png36489
banner-800x91.png25068
banner-600x69.png15621
banner-400x46.png7933

Queremos que el navegador seleccione la imagen que mejor se adapte al viewport y también al DPR. Para uno de 320 píxeles de ancho y 1 DPR se seleccionaría la de menor tamaño de 400 píxeles. Para otra con el mismo ancho de viewport pero 2 DPR debería seleccionar la de 600 píxeles que es la más cercana a los 640 píxeles que necesita. Veámos si esto es así con este juego de imágenes y usando los nuevos atributos sizes y srcset con descriptor ancho de imagen:

Ejemplo: Usando <img> con srcset y sizes

PropiedadValor
Viewport (*)?
DPR
devicePixelRatio
?
Medidas imagen
(píxeles CSS)
width, height
?
Medidas originales
(píxeles físicos)
naturalWidth, naturalHeight
?
URL
currentSrc
?
URL
src
?
Este ejemplo de soporte con los atributos srcset y sizes se ejecuta correctamente en Chrome 41 y Opera 28. Si redimensionamos la ventana se volverá a ejecutar la consulta de medios declarada en sizes. Firefox 36 también ejecuta la consulta al recargar la página, pero no cuando redimensionamos la ventana. IE11 no soporta estos nuevos atributos. Si no hay soporte aparecerá la imagen con dos cuadros rojos en la parte superior izquierda, pues usará la imagen banner-fijo2-1280x146.png del atributo src ignorando el srcset.

En el ejemplo rescatamos algunos valores mediante JavaScript. El código de esto está al final de la página y puede verlo usando el botón "Código" en la barra superior de este tema. Se presenta el ancho del viewport, el DPR, las medidas que ocupa la imagen en píxeles CSS obtenidas de las propiedades width y height del objeto imagen, las medidas originales de la imagen descargada obtenidas de las propiedades naturalWidth y naturalHeight, la URL de la imagen descargada con currentSrc y el valor del atributo clásico src.

No debemos confundir las propiedades width y height del objeto imagen con las de su estilo CSS. Cuando cualquier elemento HTML es renderizado se generan las propiedades offsetWidth y offsetHeight. Para un elemento <img> estos valores se obtienen de de los atributos width y height del elemento o de las medidas intrínsecas de la imagen al descargarla si no se declaran los anteriores. Luego se modifican con las medidas declaradas en CSS. Pero además todo <img> al cargarse en el DOM genera un objeto de JavaScript Image que alberga propiedades para el ancho y alto de la imagen, propiedades que son accesibles desde el elemento.

El código HTML del ejemplo es el siguiente:

<!-- CSS igual que el ejemplo anterior -->
<div class="banner">
    <div class="imagen-banner">
        <img src="ejemplos/banner-fijo2-1280x146.png" width="1280" height="146"
        alt="Banner que se adapta al Viewport" id="imagen-consrcset"
        sizes="100vw"
        srcset="ejemplos/banner-400x46.png 400w,
                ejemplos/banner-600x69.png 600w,
                ejemplos/banner-800x91.png 800w,
                ejemplos/banner-1000x114.png 1000w,
                ejemplos/banner-1280x146.png 1280w,
                ejemplos/banner-1750x200.png 1750w" />
    </div>
    <div class="titulo-banner" id="titulo-consrcset">
        wextensible
    </div>
</div>

La estructura HTML y todo el CSS es igual que la del primer ejemplo. Lo único que cambia es el añadido de los nuevos atributos sizes y srcset. El atributo sizes es una lista de anchos de viewport. Por ahora baste decir que su único valor 100vw indica que el tamaño de la imagen ocupara el 100% del ancho del viewport. Esto es algo parecido a lo que vimos con la adaptación del tamaño de la fuente o de las medidas del elemento imagen declaradas en el CSS para el primer ejemplo. De hecho 100vw es el valor por defecto para sizes por lo que puede omitirse.

En srcset incluimos un lista de URL separada por comas y agregando el descriptor nw que declara el ancho de la imagen. Así ejemplos/banner-400x46.png 400w le indica al navegador que esa imagen tiene un ancho de 400 píxeles lo que sabrá antes de descargarla y en su caso le permitirá seleccionarla.

En el atributo src ponemos una de las imágenes que será usada por aquellos navegadores que no soporten srcset. Para este ejemplo he puesto banner-fijo2-1280x146.png. Esta y la del primer ejemplo banner-fijo-1280x146.png son copias de banner-1280x146.png a las que le he agregado uno y dos respectivamente pequeños rectángulos de color rojo en la esquina superior izquierda. Así sabremos si el navegador está cargando la imagen desde el src o desde el srcset. Sobre esto veremos algunas consideraciones en un siguiente apartado.

Para experimentar el comportamiento de este ejemplo usaré el emulador de dispositivos móviles que trae el Developer Tools del navegador Chrome así como un dispositivo móvil real. Combinando distintos anchos y DPR, la selección del navegador debería ser la que se indica en esta tabla:

Ancho
Viewport (V)
DPRV×DPRSelección
3201320400
3201.5480400
3202640600
3752750800
360310801000

Las capturas de pantalla de estos dispositivos son las siguientes:

Selección basada en Viewport
Emulador Developer Tools Chrome dispositivo móvilImagen no disponible
Dispositivo de 320×480 a 1 DPR, según muestra el emulador del Developer Tools de Chrome. El navegador seleccionó la imagen más pequeña de 400 px.

Como se observa en las capturas y en los comentarios al pie, el navegador selecciona la imagen que más se acerca a los píxeles de ancho requeridos. Por lo tanto conseguimos que en un móvil de 320px a 1 DPR se descargue la imagen banner-400x46.png de 7933 bytes en lugar de banner-1280x146.png de 56088 bytes si no estuviéramos usando srcset.

El dispositivo con 1.5 DPR se comporta igual que el de 1 DPR. Como 320 × 1.5 = 480 y la diferencia |600-480| = 120 es mayor que |400-480| = 80, entonces el navegador seleccionará la imagen de 400 píxeles

En el móvil de igual ancho pero 2 DPR descargará banner-600x69.png que es la más cercana a los 320 × 2 DPR = 640 píxeles físicos que necesita.

También podemos usar un navegador en un ordenador de sobremesa con 1 DPR donde podemos ir redimensionando la ventana y observando que archivos de imagen se descargan:

SRCSET en un desktop
Presentación en ventana de 328 píxelesImagen no disponible
Para un ancho de ventana de 328 píxeles, el navegador selecciona inicialmente la imagen de 400 píxeles.

En esta serie de capturas de pantalla iniciamos con la ventana de un pequeño ancho de 328 píxeles. Observamos en la primera captura que se descarga el archivo más pequeño de 400 píxeles. Con 490 píxeles el navegador solicita el siguiente archivo de 600 píxeles. Lo mismo sucede en el punto de 695 píxeles que solicita el archivo de 800 píxeles.

Consideraciones con srcset y sizes

La especificación dice que cuando usemos srcset con descriptor w y en combinación con sizes el atributo src es ignorado. Por razones de compatibilidad con navegadores que no lo soporten debe ser suministrado. Con lo que he experimentado podemos considerar lo siguiente:

  1. El nivel de soporte actual de srcset obliga a suministrar el atributo src. Es necesario para Firefox 36 y IE 11, versiones actuales a Marzo 2015. No lo es para Chrome 40, Safari 8, Opera 27.
  2. En Firefox puede activarse el flag dom.image.srcset.enable en la configuración del navegador about:config para usar srcset.
  3. Para el src debemos elegir una imagen de tamaño grande con objeto de que el banner se comporte como el del primer ejemplo para los navegadores que no soporten srcset.
  4. Si el navegador ya tiene un ejemplar de mayor tamaño en caché es posible que use esa imagen en lugar de descargar una de menor tamaño. Esto es lo correcto, porque es más eficiente reescalar una imagen que descargar una nueva. Por lo tanto al hacer pruebas hemos de desactivar la caché.
  5. Si abrimos la página con un ordenador de sobremesa con un viewport grande, el navegador seleccionará una imagen de la lista que mejor se adapte, si ya no tiene una igual o de mayor tamaño en caché. Si luego vamos redimensionando la ventana a tamaños inferiores el navegador no solicitará nuevos ejemplares de menor tamaño sino que reescalará esa imagen.
  6. En las pruebas que he realizado he puesto un src con nombre de archivo distinto para los dos ejemplos: baner-fijo-1280x146.png para el primer ejemplo y baner-fijo2-1280x146.png para el segundo. Si para el segundo hubiese utilizado el mismo que para el primer ejemplo, Chrome 40 utilizaría en ciertos anchos intermedios entre puntos de ruptura el archivo del src, pues lo considera un ejemplar que ya tiene descargado en la página, dado que el primer ejemplo se ejecuta antes.
  7. He modificado las dos imágenes baner-fijo-1280x146.png y baner-fijo2-1280x146.png copiándolas del ejemplar banner-1280x146.png y agregándolese uno y dos recuadros rojos en la parte superior izquierda respectivamente. Aparte de servir para detectar visualmente en las pruebas cuando se está usando la imagen del src, también evita que la extensión PageSpeed detecte que estamos usando la misma imagen con distinto nombre de archivo. Tener dos imágenes exactamente iguales con distintas URL (es decir, en distintas ubicaciones) es un uso deficiente de recursos desde el punto de vista de la optimización. Pero en esta página es necesario hacer estas pruebas con dos nombres de archivos diferente para evitar lo explicado en puntos anteriores.
  8. Otro mensaje que puede salir en la extensión PageSpeed es ofrecer imágenes a escala cuando la imagen seleccionada es de tamaño inferior que la del src (1280×146). Esa extensión PageSpeed, cuya versión actual que tengo instalada es 2.0.4.3, está analizando el elemento <img> sin tener en cuenta las nuevas características que añade el atributo srcset. Al subir esta página al sitio en producción comprobré esto con la versión on-line PageSpeed Insights y también presentaba un mensaje similar, pero sólo para móviles con un puntuación 92 mientras que para ordenador si se consigue 100. Esta es una transcripción literal (puede ver esto en un pdf)

    Optimizar imágenes

    Formatear y comprimir correctamente las imágenes puede ahorrar una gran cantidad de bytes de datos. Optimizar estas imágenes para reducir su tamaño en 72,5 KB (reducción del 66 %).

    • Al comprimir o modificar el tamaño de http://www.wextensible.com/…agenes/ejemplos/banner-fijo-1280x146.png puedes ahorrarte 36,4 KB (un 66 % menos).
    • Al comprimir o modificar el tamaño de http://www.wextensible.com/…genes/ejemplos/banner-fijo2-1280x146.png puedes ahorrarte 36,1 KB (un 66 % menos).
    La segunda es esperable porque es del primer ejemplo no "responsive". Pero no la primera pues debería tener en cuenta el nuevo atributo srcset que no descargaría esa imagen de 1280x146 para un móvil de 320px. Por otro lado en otra herramienta de Google Developers Prueba de optimización para móviles no se observa ningún error con esta página (ver pdf). Es cuestión de seguir haciendo pruebas con estas herramientas porque es posible que aún no hayan asimilado el nuevo atributo.