Wextensible

Introducción a las consultas de medios de CSS3 (Media query)

Consulta medios width en móviles Una consulta de medios o media query en inglés es una expresión para verificar algunas características de los medios de salida. Un medio viene a ser el dispositivo en el que se presenta la información, como dispositivos visuales (screen) o salida impresa (print). El módulo estándar de CSS3 en fase REC W3C Media Queries nos explica todo lo relacionado a esta materia.

Desde HTML 4.01 y CSS 2.1 podíamos describir el tipo de medio al que se destinaba una página web, como recordamos en estos ejemplos:

En este sitio y aplicado a XHTML-1.0 más CSS2.1 puede consultar el atributo HTML media o las reglas @media y @import. Esto sigue siendo igual en HTML5, incorporándose ahora la posibilidad de realizar al mismo tiempo consultas sobre características de ese medio. Por ejemplo preguntar qué ancho tiene la pantalla o cuál es la orientación del dispositivo móvil (vertical u horizontal) entre otras. Estas consultas nos permitirán configurar el estilo apropiado para cada dispositivo. Y esto es una de las bases para el diseño adaptativo (responsive design).

La regla @media antes tenía la sintaxis @media LISTA_DE_MEDIOS {...} donde la lista de medios era una lista de tipos como screen, projection por ejemplo. Al final tendríamos algo como:

@media screen, projection {
    div.titulos {
        font-family: Arial;
    }
}

Este ejemplo aplicaría un estilo de fuente al elemento de clase titulos en los monitores y proyectores. En cualquier otro medio no se aplicaría este estilo. Ahora esa lista de medios (media list) se podría convertir en una lista de consultas de medios (media query list). Un posible ejemplo podría ser:

@media screen and (min-width: 800px), projection {
    div.titulos {
        font-family: Arial;
    }
}

El estilo sería aplicado a los monitores con ancho igual o superior a 800 píxeles y a todos los proyectores. Al resto de dispositivos y a los monitores con ancho inferior no se les aplicaría ese estilo.

Sintaxis de las consultas de medios

Una consulta de medios, que siempre devolverá al navegador un valor verdadero o falso, tiene la siguiente sintaxis:

[only|not]? tipo [and (característica [ : valor]?)]*

El tipo de medio all aplica a todos los medios. En ese caso podemos omitir las partículas all y and. Por ejemplo, es equivalente:

@media all and (max-width: 800px) {
    div.miclase {
        color: red;
    }
}

a esto otro:

@media (max-width: 800px) {
    div.miclase {
        color: red;
    }
}

Características de medios (Media features)

Un resumen de las características de los medios se expone a continuación. En la especificación oficial puede encontrar más detalles, pero por ahora sólo me voy a limitar a conocer lo que hay para tener una idea general.

CaracterísticaValorMín, maxDescripción
widthUna longitudAncho del viewport
heightUna longitudAlto del viewport
device-widthUna longitudAncho del monitor
device-heightUna longitudAlto del monitor
orientationportrait o landscapeLa orientación es portrait cuando el valor de height es mayor que el de width. En otro caso será landscape.
aspect-ratioUna relaciónEs la relación width/height
device-aspect-ratioUna relaciónEs la relación device-width/device-height
colorUn enteroEl número de bits por componente de color.
resolutionUna resoluciónLa densidad de píxeles del dispositivo.

Hay otras características que no se incluyen en la tabla anterior como color-index, monochrome, scan y grid. Los valores de longitud son los ya definidos en CSS-2.1, por ejemplo 800px o 20em. Los nuevos valores introducidos son la relación (ratio) que es algo como 16/9 y la resolución (resolution) que es un número entero positivo seguido de las unidades dpi o dpcm. Son puntos por pulgada y por centímetro respectivamente para medir la densidad de píxeles del dispositivo.

Todas estas características nos servirán para actuar sobre nuestras páginas adaptándolas a los distintos dispositivos. Pero hay muchas cosas que ver y probar. En este tema sólo miraré lo relacionado con las medidas del viewport y del dispositivo que afectan a la primeras característica width de la tabla anterior.

Para entender el concepto de viewport puede consultar el apartado bloque de contención inicial y el viewport del tema de adaptación de dispositivos.

Estructura de página web con bloques en línea

Las consultas de medios se pueden usar para adaptar la estructura de la página al ancho de la pantalla del dispositivo. Hay muchas formas de estructurar una página. En el tema cómo se hace la estructura de una página web puede ver por ejemplo la estructura elástica basada en el uso de la propiedad float para flotar los contenedores laterales a izquierda y derecha, a la vez que todas las medidas se hacían relativas al tamaño de la fuente. Esta estructura es la que he venido usando en este sitio, aunque limitada sólo a la página de inicio. Para llevar a cabo esas flotaciones de los laterales, por ejemplo utilizando sólo el lateral derecho, con respecto al contenido que sería el bloque central hay que disponer el HTML así:

<div id="lateral">
    ...
</div>
<div id="contenido">
    ...
</div>

Aplicaríamos un CSS como el siguiente:

div#lateral {
    width: 6.25em;
    height: 100%;
    padding: 0.3125em;
    float: right;
    }
div#contenido {
    margin-right: 7.875em;
    padding: 0.3125em;
    }

El ancho del lateral queda fijado a 6.25em, unos 100px con una fuente de 16px. Flotamos a derecha y damos un margen derecho al contenedor algo superior al ancho del lateral más sus bordes de 0.3125em. Esto funcionaría correctamente en un dispositivo de pantalla pequeña, pero precisamente ese es el problema. Con un ancho de pantalla de 320px que tienen muchos móviles, quitando los 100px del lateral y los rellenos del contenido, nos quedamos con sólo 210px para este cuerpo central. Dependiendo de lo que vayamos a poner en el contenido, quizás es preferible pensar que para anchos de pantalla por debajo de 640px ese contenido ocupe todo el ancho disponible y no mostrar el lateral.

Es entonces cuando entran en juego las consultas de medios. Podemos detectar el ancho del dispositivo y actuar en consecuencia. Usualmente se reubica el lateral para que siga al contenido en flujo estático. Aparecerá entonces el contenido ocupando todo el ancho de pantalla y a continuación el lateral en la parte baja de la página. Sería tan fácil como hacer float: none para el lateral y quitarle el margin-right al contenido, pero el problema es que ese lateral está antes del contenido. Y no es fácil desplazarlo para que fluya estáticamente después del contenido.

Esto nos lleva a considerar una estructura de bloques en línea, haciendo uso de la propiedad y valor display: inline-block. Los elementos de bloque causan un salto de línea siempre. Pero los inline-block permanecen en la misma línea si caben. Si no caben se saltan de línea. Entonces la estructura de nuestro contenido y lateral a la derecha quedaría así:

<div id="contenido">
    ...
</div>
<div id="lateral">
    ...
</div>

Observe que ahora si tenemos el lateral después del contenido. Completamos con el siguiente CSS:

div#contenido {
    display: inline-block;
    width: auto;
    margin-right: 192px;
    vertical-align: top;
}
div#lateral {
    display: inline-block;
    width: 192px;
    margin-left: -192px;
}

Ese código es parte del ejemplo expuesto más abajo, con un lateral un poco mayor, de 192px. Ambos contenedores están dispuestos como bloques en línea. El contenido tiene un ancho auto, que no es necesario especificarlo pues es su valor por defecto. Llenará todo el ancho disponible. Para albergar el lateral en la misma línea del contenido, éste ha de tener un margen derecho de 192px, igual que el ancho del lateral. Mientras que ese lateral debe tener un margen izquierdo negativo del mismo valor. Con este margen negativo forzamos el lateral para que se disponga en la misma línea que el contenido, al que damos el mismo margen pero positivo para que no se alargue más alla de donde empieza el lateral.

Un ejemplo de la estructura anterior es la siguiente captura de pantalla en un monitor de un ordenador de sobremesa:

Estructura con bloques en línea, contenido y lateral derecho

Modificando la estructura de la página web con una consulta de medios

La captura de pantalla anterior es de un ejemplo con la ventana del monitor redimensionada a 641px. Pues ese ejemplo ya incluye una consulta de medios tal que para anchos de 640px y menores obtendremos otra estructura en la parte superior de la página:

Estructura con bloques en línea, contenido y lateral derecho, aplicando consulta medios

La botonera o menú de la cabecera se oculta apareciendo un icono de tres franjas horizontales a la derecha del título de la página que nos permitirá acceder a esa botonera. Lo importante es que ahora estamos en dispositivos móviles con dimensiones reducidas y hemos de ocultar o trasladar de la parte superior de la página todos aquellos contenidos que no sean principales. El lateral se ha trasladado a la parte inferior de la página:

Estructura con bloques en línea, contenido y lateral derecho, aplicando consulta medios

La estructura básica HTML de los cuerpos del contenido y lateral sería la siguiente:

<div id="contenido">
    ...
</div>
<div id="lateral">
    <div class="submenu">
        ...
    </div>
    <div class="submenu">
        ...
    </div>
    ...
</div>

Siguiendo el criterio de móvil primero (mobile first), dispondremos el CSS necesario para atender en primer lugar los dispositivos móviles y luego aplicaremos consultas de medios para ordenadores de sobremesa:

div#contenido {
    display: block;
    vertical-align: top;
    text-align: left;
}
div#lateral {
    display: block;
    margin-bottom: 0.5em;
    text-align: left;
}
div.submenu {
    display: inline-block;
    padding: 0;
    vertical-align: top;
}

@media screen and (min-width: 641px){
    div#contenido {
        display: inline-block;
        margin-right: 192px;
    }
    div#lateral {
        display: inline-block;
        width: 192px;
        margin-left: -192px;
    }
    div.submenu {
        padding-left: 10px;
        text-align: center;
        margin-bottom: 0.5em;
    }
}

El contenido y lateral se declaran inicialmente como elementos de bloque con display: block. Esto es en principio para cualquier tipo de dispositivo, haciendo que el lateral salte de línea y se ubique por debajo del contenido, antes del pie de la página que es de color y borde igual que la cabecera. Los cinco contenedores del lateral, de clase submenu, se ubican como bloques en línea (display: inline-block). Si tienen anchos declarados, como sucede con los cuatro primeros, aparecen uno tras otro en una línea mientras quepan. El de color azul fuerte ya no cabe en esa línea y salta a la siguiente. El quinto, con fondo gris, no tiene ancho declarado que equivale a decir que tiene width: auto, por lo que ocupará una línea completa.

Finalmente ponemos en el CSS una consulta de medios para dispositivos iguales o mayores de 641px. Como se comenta en el apartado anterior, ahora contenido y lateral pasan a inline-block, dándole un ancho fijo al lateral de 192px. Forzamos a que estén juntos poniendo un margen derecho del contenido igual que el margen izquierdo del lateral pero negativo y con el valor del ancho del lateral.

El motivo de escribir el CSS pensando primero en móviles es que ejecutar una consulta de medios como la anterior supone sobrescritura de propiedades. Y esto siempre da lugar a un coste de tiempo añadido. Cuando un navegador móvil de ancho inferior a esos 641px llegue a la consulta simplemente ignorará sus declaraciones pues no le afectará. En cambio un ordenador de sobremesa o cualquier dispositivo con una pantalla de mayor tamaño deberá aplicar el estilo sobrescribiendo las propiedades. Pero al menos ese dispositivo tendrá más recursos para afrontar esta tarea que un móvil de reducido tamaño.

Mostrando y ocultando la botonera o menú principal

Botón menú Para cualquier dispositivo la botonera o menú principal (ul#botonera-cabeza) en la cabecera de la página no aparecerá inicialmente, pues le ponemos display: none. En cambio presentaremos el icono o botón "≡" con tres franjas horizontales (div#boton-menu) que nos permitirá acceder a ese menú. Cuando la pantalla sea igual o mayor que 641px sobrescribimos para que se muestre la botonera con block!important mientras que el botón de menú se oculta con none!important.

ul#botonera-cabeza {
    display: none;
}
div#boton-menu {
    display: block;
    cursor: pointer;
    float: right;
}
@media screen and (min-width: 641px){
    ul#botonera-cabeza {
        display: block!important;
    }
    div#boton-menu {
        display: none!important;
    }
}

El uso de !important hará que se ejecute en cualquier caso, pues puede haber un problema al usar el siguiente JavaScript para mostrar y ocultar la botonera:

<script>
    window.onload = function(){
        try {
            document.getElementById("total").addEventListener("click", 
                abrirCerrarBotonera, false);
        } catch(e){}
    };

    function abrirCerrarBotonera(event) {
        var este = event.target;
        var botonera = document.getElementById("botonera-cabeza");
        var disp = document.defaultView.getComputedStyle(botonera,
                null).getPropertyValue("display");
        if ((este.id=="boton-menu")||
              ((este.id!="boton-menu")&&(disp=="block"))){
              if (disp=="none"){
                botonera.style.display = "block";
            } else {
                botonera.style.display = "none";
            }
        }
    }
</script>

Este script agrega estilo en línea, es decir, añade propiedades en el atributo style del elemento. El estilo en línea tiene mayor especificidad que el que aparece en una regla de un elemento <style> o archivo CSS externo, a no ser que agreguemos !important al valor de la propiedad. Por lo tanto si ponemos con el script display: none en estilo en línea con un tamaño de pantalla menor, cuando redimensionemos la ventana a mayor tamaño sucederá que el estilo de la consulta con el valor display: block no sería aplicado a menos que lo hagamos !important. De forma similar hacemos también para el botón de menú que muestra y oculta la botonera o menú principal.

En cuanto al script, un manejador de evento es aplicado a un elemento <div id="total"> que contiene toda la página. Con esto cuando pulsemos el botón se abrirá la botonera o menú. Al pulsarlo otra vez o al pulsar en cualquier parte de la página se cerrará. Consultamos el estilo actual de la botonera con getComputedStyle() para saber si está abierta o cerrada.