Adaptación de dispositivos

El bloque de contención inicial y el viewport

Navegador móvil Una web puede funcionar bien en un ordenador de sobremesa, pero cuando pasamos a un dispositivo móvil hay muchas cosas que tener cuenta. Una de ellas son las propias dimensiones de la pantalla que nos obliga a hacer una adaptación de dispositivo de nuestras páginas. Esto será un punto de partida para lograr el diseño adaptativo (responsive design) de un sitio web.

Empezaremos recordando algunos conceptos como el bloque de contención de un elemento HTML, que viene a ser otro elemento padre que limita y condiciona la posición y tamaño de ese hijo. Para el elemento raíz <html> recibe el nombre de bloque de contención inicial. Supongamos un HTML como el siguiente:

<!DOCTYPE html>
<html lang="es">
<head>
    <title>Ejemplo</title>
</head>
<body>
    <div>Texto</div>
</body>
</html>

El bloque de contención del elemento <div> es <body> y el de éste es <html>. Y finalmente el viewport es el bloque de contención del elemento <html>. Pensando en ordenadores de sobremesa, el término viewport es algo que otras veces denominamos como la ventana del navegador, específicamente el área del navegador destinada a presentar la página web. Esta superficie es redimensionable pues el usuario puede cambiar el tamaño de la ventana en un ordenador de sobremesa.

El término viewport estaba definido en CSS-2.1, tanto en la definición del bloque de contención inicial, donde dice que ese bloque tiene las dimensiones del viewport y es anclado al lienzo de origen. Una página se renderiza en un lienzo y el viewport es una ventana a través de la cual vemos ese lienzo o una parte del mismo. Se refiere a que una página puede ser mayor que el viewport, en cuyo caso aparecerán unas barras de desplazamiento para llegar a las partes que quedan por fuera de la ventana.

Un navegador de un ordenador dispone de un viewport igual al tamaño de la ventana. Si la redimensionamos el navegador recalculará todas las dimensiones para adaptarlas al nuevo tamaño. Es decir, adaptará la diagramación o layout de la página a las nuevas dimensiones de la ventana. Desde el punto de vista del autor de la página, lo que más le interesa es el ancho de la página que puede verse afectado por el ancho de la ventana. Es deseable que el contenido horizontal aparezca completo y que no se tenga que acceder con barras de desplazamiento. Mientras que el contenido vertical si puede ser mayor que el alto de la ventana.

Las páginas web desarrolladas para ordenadores de sobremesa pueden estar optimizadas para resoluciones horizontales superiores a 800 píxeles, dado que los monitores de sobremesa modernos tienen con toda seguridad más de esa resolución. Por otro lado los dispositivos móviles pueden tener entre 300 y 400 píxeles de ancho y cuando intentamos visualizar esa página pudieran darse situaciones donde aquella optimización deja mucho que desear. No hay posibilidad de redimensionar la ventana como en un ordenador y el usuario sólo puede actuar escalando la página.

Para solventar lo anterior y tratar de optimizar la presentación de la mayor parte de páginas web ya en uso, los móviles manejan un viewport visual y otro viewport de layout. El de layout es un viewport "virtual", algo como un lienzo donde el navegador inicialmente dispone los elementos. Suelen tener valores como 800 o 950 píxeles de ancho. El navegador móvil recibe la página y la renderiza en ese ancho "virtual", luego aplica el escalado adecuado (zoom) para que se ajuste al ancho real del dispositivo presentándolo en el viewport visual que viene a ser el área de la pantalla destinada a presentar la web. El ancho es el de la propia pantalla, mientras que el alto puede ser menor debido a las barras de botones propias del móvil (barra de estado, de URL y barra de botones al pie). Por ejemplo, el iPhone tiene una pantalla de 320×480 pero finalmente el área para la presentación se queda en 320×356 con orientación vertical (portrait) y 480×208 con orientación horizontal (landscape), tal como se explica en esta página de Safari Developer.

Cuánto mide un píxel

Cuando trabajamos con CSS no nos interesa saber cuánto mide un píxel o incluso las medidas de longitud de la pantalla de un dispositivo. Pero este apartado es un ejercicio destinado a comprender el problema de la resolución y la densidad de píxeles. Al final veremos que los píxeles CSS son verdaderamente independientes del dispositivo. Y es bueno saberlo, porque cuando pongamos algo de CSS como width:300px no nos preocuparemos de cómo se va a ver en tal o cual dispositivo.

En CSS las longitudes se manejan en píxeles CSS y deben considerarse independientes del dispositivo. Por otro lado una pantalla tiene unas medidas que se manejan en píxeles de dispositivo (device pixels). En CSS-2.1 ya se especificaba un procedimiento para definir el píxel de referencia o píxel CSS como el ángulo visual de un píxel en un dispositivo con una densidad de píxeles de 96 dpi (puntos por pulgada) a una distancia de 28 pulgadas (71 cm) hasta el ojo, lo que supone un ángulo de 0,0213 grados. De esta forma 1 px equivale a 0,26 mm (1/96 pulgada) en un monitor de sobremesa. Podemos simplificar y pensar en un píxel de dispositivo de un monitor como el mínimo cuadrado de luz de 0,26 × 0,26 mm. El objetivo final es buscar una dimensión física del píxel para que el usuario no pueda percibir el salto entre píxeles a la distancia que usualmente visualiza un determinado dispositivo de pantalla.

Un monitor de sobremesa lo vemos usualmente a una distancia de 70 cm. Mientras que un móvil se maneja a una distancia menor. Por lo tanto hay que disminuir el tamaño del píxel por debajo de los 0,26 mm, lo que se traduce en aumentar la densidad de píxeles. Un móvil de resolución normal como el Samsung Galaxy Ace tiene una ancho de pantalla de unos 49 mm (1,929 pulgadas) y 320 píxeles de ancho, lo que supone 320 / 1,929 = 165,9 dpi de densidad de píxeles en ancho. En este dispositivo un píxel mide 49 / 320 = 0,153 mm de ancho (mínimo cuadrado de luz de 0,153 × 0,153 mm).

En este apartado y referente a los móviles al decir ancho de pantalla, ancho de píxel o densidad de píxel en ancho me refiero a la orientación portrait, siendo el ancho la medida de menor tamaño. En la orientación landscape el ancho sería la medida de mayor tamaño. Realmente la densidad de píxeles que aparece en las especificaciones se calcula sobre la diagonal.

La medida de longitud del ancho y alto de la pantalla de los móviles es un dato que no suele encontrarse en las especificaciones. Se presenta siempre la medida de la longitud de la diagonal. Las medidas resaltadas en amarillo que aparecen en este apartado las tomé y/o comprobé directamente midiéndolo con una regla en el dispositivo. El resto de datos son tomados de las especificaciones del fabricante.

Tanto en el monitor de sobremesa como en el móvil de resolución normal señalados antes un píxel CSS se representa con un píxel de dispositivo. Cuando especificamos en CSS un ancho de 300 píxeles para un elemento, ambos dispositivos usan un píxel de dispositivo por cada píxel CSS a un zoom del 100%. Por lo tanto usarán 300 píxeles de dispositivo. En el monitor de sobremesa con 0,26 mm/px aquel elemento ocupara un ancho de 0,26 × 300 = 78 mm, mientras que en el móvil con 0,153 mm/px tendrá un ancho de 0,153 × 300 = 45,9 mm. Si incrementamos el zoom al 200%, usarán 600 píxeles de dispositivo para los 300 píxeles CSS, permaneciendo éstos constantes sea cual sea el zoom.

Los 0.26 mm del tamaño de un píxel para un monitor de sobremesa que se declara en la especificación CSS es un valor de referencia aproximado. Yo tengo un monitor HP1940 con una resolución de 1280 × 1024 píxeles. Mido el ancho de la pantalla y resulta de 376 mm. Por lo tanto la densidad de píxeles es de 376 / 1280 = 0,294 mm/px, algo superior a los 0,26 de la especificación. Una medida de 300px ocupa 300 × 0,294 = 88 mm.

Pero hay otros dispositivos de alta resolución como puede ser un impresora, que puede tener unos 600 dpi de resolución máxima. Un píxel de dispositivo de impresora (o punto) para esa resolución mide entonces unos 25,4 / 600 = 0,0423 mm. A una resolución para representar píxeles CSS de 96 dpi equiparable a un monitor, se necesitan unos 5 píxeles horizontales de dispositivo de impresora para conseguir un píxel CSS de 0,21 mm de ancho (5 × 0,0423 = 0,21 mm), una longitud similar a los 0,26 mm que mide un píxel CSS y de dispositivo en el monitor. Entonces el píxel CSS será presentado en el papel impreso usando un cuadrado de 5 × 5 píxeles de dispositivo de impresora. Aquel elemento de 300 píxeles CSS de ancho medirá 300 × 0,21 = 63 mm de ancho en el papel impreso.

Los móviles de alta resolución como el IPhone 4 y 5 tienen una densidad de píxeles de 326 dpi, exactamente el doble del IPhone 3 de resolución normal con unos 163 dpi. La pantalla del IPhone 5 tiene una resolución horizontal de 640 px, también exactamente el doble que el IPhone 3, por lo que usará 2 píxeles de dispositivo para presentar 1 píxel CSS de ancho. Tiene un ancho de pantalla de 51,6 mm lo que nos da 51,6 / 640 = 0,081 mm de longitud de un píxel de dispositivo. Así dos píxeles de dispositivo serán unos 2 × 0,081 = 0,16 mm, valor similar al de un móvil de resolución normal de 165 dpi. El elemento de 300 píxeles CSS de ancho medirá aproximadamente unos 300 × 2 × 0,081 = 48,6 mm. Aunque la resolución en ancho es de 640 píxeles de dispositivo, esta se transforma en 320 píxeles CSS al usar una relación de 2×1.

El Samsung Galaxy S4 tiene una densidad de 441 dpi con una resolución en portrait de 1080 × 1920 píxeles. Tiene un ancho de pantalla de 62,1 mm por lo que cada píxel de dispositivo mide horizontalmente 62,1 / 1080 = 0,057 mm. Para presentar un píxel CSS de unos 0,16 mm equiparable al de un móvil de resolución normal de 165 dpi necesitará 3 píxeles (redondeando la operación 0,16 / 0,057 = 2,8). El elemento de 300 píxeles CSS de ancho medirá aproximadamente unos 300 × 3 × 0,057 = 51,3 mm. La resolución en ancho es de 1080 píxeles de dispositivo que se transforman en 360 píxeles CSS al usar una relación de 3×1.

Estas altas densidades de píxeles tienen como resultado una presentación del texto de muy alta calidad. Pero al mismo tiempo podemos tener problemas con las imágenes. Supongamos una imagen de 300 píxeles de ancho. En un móvil con una relación 1×1 no hay problema, pero si la relación es 3×1 la imagen se dibujará con 900 píxeles de dispositivo y no tenemos más que 300 disponibles. El navegador replicará los píxeles para completar lo que falta, pero esto empeora la calidad de la imagen. Tendríamos que pasarle una imagen de 900 píxeles de ancho al Samsung Galaxy S4 para obtener una calidad equiparable a una imagen de 300 píxeles de ancho en un móvil de resolución normal. Para resolver este problema hay varias técnicas que tendré que aprender más adelante.

Por último podemos resumir el viewport visual en píxeles de dispositivo y píxeles CSS de los modelos de móvil comentados en este apartado:

  • El Samsung Galaxy Ace tiene un viewport visual de 320×480 píxeles de dispositivo y 320×480 píxeles CSS en orientación portrait, pues la relación píxeles dispositivo a píxeles CSS es 1×1
  • El IPhone 5 tiene un viewport visual de 640×1136 píxeles de dispositivo y 320×568 píxeles CSS en orientación portrait. Relación 2×1.
  • El Samsung Galaxy S4 tiene un viewport visual de 1080×1920 píxeles de dispositivo y 360×640 píxeles CSS en orientación portrait. Relación 3×1.

Saber el tamaño del viewport visual en píxeles CSS de los móviles es el único dato importante para el autor de CSS. Pues el navegador usará esas medidas al 100% de zoom para dibujar nuestro CSS. En la página Viewport Sizes (i-Skool) puede ver una lista de tamaños de viewports visuales en píxeles CSS de una lista de dispositivos.

Probando el viewport con un ejemplo

Creo que será mejor hacer un ejemplo y presentarlo aquí.La página de ejemplo viewport-sin-adaptar.html es un simple HTML con una imagen de 300 × 212 píxeles flotada a la iquierda de unos párrafos de texto. Al presentarla en un terminal móvil como el Samsung Galaxy Ace que tiene un viewport de 320 × 480 veremos algo como esto:

Ejemplo viewport 800 píxeles

Esa captura de pantalla de 320 × 480 píxeles la obtuve en ese móvil, tras lo cual la reduje de tamaño y la pasé a escala de grises para que no ocupe demasiado, pues sólo nos interesa observar como el navegador del móvil dispone la estructura de la página. Y vemos que el contenido se hace demasiado pequeño, con la imagen junto al texto muy reducida y un tamaño de letra prácticamente imposible de leer en ese dispositivo. El usuario aún puede utilizar el zoom para acceder a los contenidos, pero la presentación inicial podría ser más legible si aparece así:

Ejemplo viewport 800 píxeles

Esta segunda captura de pantalla es la del ejemplo viewport-adaptado.html, una página cuya diferencia con la anterior es que incluye un elemento <meta name="viewport" content="width=device-width, initial-scale=1"> en la cabecera. Esto fuerza al navegador para que utilice como viewport de layout el mismo que el visual, en este caso los 320 píxeles de ancho para ese dispositivo. En otro caso, como en el primer ejemplo, el navegador dispone inicialmente la página en un viewport de layout de 800 píxeles de ancho y luego aplica un escalado para meterlo en los 320 píxeles de ancho del layout visual resultando unos contenidos muy reducidos. Note que ahora la imagen previa al texto tiene 300 píxeles CSS (y de dispositivo) de ancho. Por la izquierda hay un margen de 8 píxeles que tiene el elemento <body> por defecto, al igual que por la derecha, donde la franja es de 12 píxeles (8 del margen del <body> más 4 hasta completar los 320).

Los móviles recientes como el Samsung Galaxy S4 tiene una densidad de píxeles muy alta, unos 441 píxeles por pulgada haciendo un total de 1080 × 1920 píxeles. El ejemplo primero se vería así en éste móvil:

Ejemplo viewport 800 píxeles

Mientras que la página de ejemplo que usa la etiqueta <meta> se ve así:

Ejemplo viewport 800 píxeles

Estas capturas de pantalla obtenidas en el Galaxy S4 son imágenes de 1080 × 1920 píxeles, también reducidas y pasadas a escala de grises para aligerarla (y mucho) de peso. Este móvil tiene un viewport visual de 360 píxeles CSS de ancho con zoom al 100%. Vemos que a la altura de la imagen previa al texto esos 360 píxeles CSS son la suma de:

  • 8 del margen izquierdo del <body>
  • 300 de la imagen
  • 44 a la derecha de la imagen, suficiente para quepa la primera palabra "Este" del primer párrafo.
  • 8 del margen derecho del <body>

La regla @viewport y la meta-etiqueta viewport

Para ajustar el viewport visual y de layout nos aconsejan incluir la meta-etiqueta viewport. En Mobile Web Application Best Practices nos dicen que hemos de poner en el <head> lo siguiente:


<meta name="viewport" content="width=device-width, initial-scale=1.0" />

Esto informará al navegador que siempre presente la página sin escalar y usando un viewport de layout con tamaño igual al de la pantalla. El registro de las meta-etiquetas se encuentran en el sitio whatwg.org. La define como una etiqueta para especificar ciertas características que serán aplicadas al bloque de contención inicial del documento. Para más detalles nos remite a la especificación de la nueva regla CSS @viewport en el documento CSS Device Adaptation (15 Sep 2011), aún en fase Working Draft vigente al presente (Agosto 2013). Hay un borrador del editor CSS Device Adaptation (Editor's Draft 30 May 2013) que viene a ser un documento de trabajo donde se pueden reflejar modificaciones que podrían o no consolidarse en el Working Draft.

Introduce la nueva regla @viewport que viene a funcionar como el elemento <meta name="viewport">. Incluso hay algunos apartados para mostrar equivalencias entre ambos métodos. En el punto 10.4 dice que ese elemento <meta>, en relación con el tratamiento que debe darle el navegador, será ubicado en el procedimiento de estilo en cascada como si fuese un elemento <style> que sólo contiene una regla @viewport.

Y es que si uno tiene ya un sitio con muchos documentos HTML es preferible agregar una regla a un único archivo CSS que poner ese meta en todos los documentos. En mi sitio uso el estilo CSS del archivo cabeza-pie.css en todos los documentos. Por lo tanto es ahí donde podría (en principio) incluir la regla @viewport, pues con sólo lo siguiente actualizaría todo el sitio:

@viewport {
    width: device-width;
    zoom: 1.0;
    }

La sintaxis es muy simple. Se trata de una recopilación de propiedades, o mejor dicho, descriptores de características del dispositivo, como las siguientes relacionadas con longitudes del viewport:

  • min-width, max-width, min-height y max-height especifican las mínimas y máximas medidas del viewport que serán usadas para establecer el bloque de contención inicial. Permiten un valor de longitud del viewport que se describe más abajo. El valor inicial es auto.
  • width y height son descriptores resumidos de los anteriores, permitiendo uno o dos valores de longitud del viewport. Así algo como width: 320px 600px es el resumen de min-width: 320px; max-width: 440px. Si sólo se usa un valor se aplicará tanto al mínimo como al máximo.
  • Otros descriptores son zoom, max-zoom, min-zoom, user-zoom y orientation.

Los valores longitudes de viewport para los descriptores de dimensiones son:

  • auto, donde el valor usado es calculado a partir del valor de otros descriptores de acuerdo con un procedimiento que también se detalla en la especificación.
  • device-width y device-height son el ancho y alto de la pantalla respectivamente en píxeles CSS
  • Una longitud tal como es permitida en CSS
  • Un porcentaje relativo al ancho del viewport inicial con un zoom de 1.0

Pero el borrador del 30 May 2013 de la especificación señalada antes no incluye device-width/height como un valor permitido. Además no todos los navegadores implementan aún @viewport, creo que sólo Opera Mobile @-o-viewport (en Presto 28) e IE10 @-ms-viewport,ambos aún con prefijo. Y estos si siguen aceptando device-width como un valor permitido, tal como podemos ver en ejemplos expuestos en esos documentos.

Por lo tanto dado que todo esto del @viewport aún está "verde" es mejor usar el elemento <meta name="viewport"> que si es soportado por la mayoría de navegadores. Al menos hasta que la especificación CSS se consolide, pues indudablemente resulta que es el lugar que parece más natural donde incluir esta característica. Para no tener que modificar todos los documentos del sitio, intentaré insertar ese elemento con JavaScript. El archivo general.js también se vincula en todos los documentos del sitio. Ahí incluiré lo siguiente:

En Diciembre 2013 he actualizado este sitio en profundidad para que se adapte mejor a cualquier dispositivo. Dado que he tenido que modificar todos los documentos del sitio, he aprovechado para incluir el meta viewport en lugar de usar la siguiente función.
var Wextensible = Wextensible || {};

Wextensible.general = {

/* Crea un elemento meta viewport */
viewport: (function(){
    var metaViewport = document.createElement("meta");
    metaViewport.name = "viewport";
    metaViewport.content = "width=device-width, initial-scale=1.0";
    document.getElementsByTagName("head")[0].appendChild(metaViewport);
    return metaViewport.content;
})(),

    .......

};

El objeto Wextensible.general agrupa funciones y variables de uso genérico para el sitio. El módulo no interaccionaba con el DOM ni ejecutaba funciones para evitar bloquear la página en la carga. Pero en este caso si lo hace, agregando la meta-etiqueta del viewport. El propósito es que en cuanto la regla @viewport sea un estándar eliminar esta funcionalidad de aquí e incluirla en el archivo CSS señalado que también lo tengo vinculado en todos los documentos.