DÍA 22 / 2014

Element Query

Desde la llegada de los mediaqueries hemos elegido breakpoints basados en el viewport sin saber que eso no es lo que en realidad queremos. La tendencia a modularizar todo nos obliga a ir más allá y buscar un query basado en el elemento mismo. Pero repasemos un poco que es todo eso de modularizar y porqué ahora resulta que los Mediaqueries no nos bastan.


Hoy en día leemos en todos lados que nuestro CSS debe ser modular, modular y responsive.

Esto viene dado por la necesidad en proyectos grandes (y muchas veces, pequeños) de flexibilizar los elementos del CSS para poder reutilizarlos, tanto selectores como mixins de preprocesadores; tomemos un pequeño ejemplo muy común: Muchas veces queremos centrar el contenido de nuestra en la pantalla en resoluciones grandes y solemos agregar un <div> que contenga nuestro <header>, nuestro <main> y nuestro <footer>, con un ancho máximo para que mantenga todo el contenido sin sobrepasar un ancho dado - comúnmente con la clase wrapper:

.wrapper {
  width:100%;
  max-width:1890px;
}

Y con eso logramos que el contenido de nuestra web no pase de 1890px de ancho.

Por supuesto que querremos centrar todo agregando un margin-left y margin-right con el valor auto:

.wrapper {
  width:100%;
  max-width:1890px;
  margin-right: auto;
  margin-left: auto;
}

Ya tenemos un contenedor que agrupa todo el contenido de la web y limita su anchura, aparte de centrarlo.

Esto puede generar un pequeño problema si queremos, por ejemplo, colocar el header del tamaño total del viewport y el viewport sobrepasa los 1890px. Tendríamos que modificar nuestro HTML y redefinir las propiedades CSS. Esto es una pérdida de tiempo que podríamos haber evitado si planeamos con antelación que los bloques pueden crecer independientemente en ciertas páginas o secciones.

Hay que evitar crear castillos de naipes con el CSS y saber que podemos modificar estilos de un bloque en concreto sin que este afecte al resto del layout o pierda consistencia la atmósfera de diseño.

Volviendo al ejemplo anterior, si eliminamos elemento .wrapper que envuelve todos los elementos de la web y creamos una clase .page-maxwidth con el mismo valor podemos asignarla a cualquier bloque para que se comporte como deseamos: centrados y con un ancho máximo de 1890px.

<header>...</header>
<main class="page-maxwidth">...</main>
<footer>...</footer>

De esta manera estamos convirtiendo una propiedad en un pequeño módulo de código reutilizable con un par de ventajas: No tendremos que repetir ese código y evitamos asignar propiedades a bloques concretos en vez de crearlas para reutilizarlas.

Si se nos es más cómodo, podemos convertir esa clase en un placeholder y extenderla en el selector que queremos aplicarla.

Pero podemos ir más allá y crear un mixin para simplificar estas variaciones de manera que el tamaño predeterminado sea 100% y en todos los que queremos reducir incluimos en mixin en el CSS. De esta manera no tendremos que realizar cambios al HTML o en las declaraciones CSS:

@mixin : page-maxwidth($width : 1890px) {
  max-width : $width;
  margin-right : auto;
  margin-left : auto;
}
Los preprocesadores como SASS nos permiten crear mixins y operar con variables dentro del CSS, lo cual representa una ventaja enorme a la hora de crear una arquitectura CSS con respecto al CSS plano o *vanilla CSS

Gracias a esa posibilidad de jugar con variables en los preprocesadores, en este mixin podemos permitir que el ancho máximo sea declarado al usarlo, es decir, creamos un mixin paramétrico que nos permite definir el valor del ancho máximo. Como normalmente el ancho va a ser el mismo en la mayoría de los elementos, ya podemos tener predefinida esa variable y ajustarla si el valor cambia:

$page-maxwidth-size : 1890px;

@mixin : max-width($width : $page-maxwidth-size) {
  max-width : $width;
  margin-right : auto;
  margin-left : auto;
}

Clases reutilizables

Hemos simplificado lo que en un principio era un <div> que envolvía nuestro contenido hasta simplemente tener que declarar un mixin en cada una de las declaraciones CSS que va a servirnos siempre que lo necesitemos.

header {
  @include max-width()
}

Simple y flexible. Soporta que cambiemos el ancho ya que el mixin nos ofrece un parámetro para casos en los que el ancho no sea ni 100% ni el definido en nuestras variables (1890px en este caso):

header {
  @include max-width(1024px)
}

¿Por qué queremos crear un mixin que tendremos que escribir en cada regla de CSS donde lo queremos usar en vez de añadir una simple clase en cada elemento HTML al que queremos aplicar esas características?

La respuesta fácil es: Porque las características pueden variar de un elemento a otro y el mixin soporta cualquier cambio. Si añadimos una clase a un elemento y ese elemento debe comportarse en cierto momento de manera diferente - digamos un header que en cierto punto deba medir 100% en vez de 1890px - tendríamos que modificar la clase y estos afectaría al resto de elementos que usan esa clase. El mixin nos ofrece esa diversidad.

Como este mixin hay muchos otros elementos que queremos reutilizar con frecuencia en nuestro código y que no queremos escribir una y otra vez. Todo esto viene dado por una filosofía DRY (Don’t Repeat yourself):

“Cuando el principio DRY se aplica de forma eficiente los cambios en cualquier parte del proceso requieren cambios en un único lugar. Por el contrario, si algunas partes del proceso están repetidas por varios sitios, los cambios pueden provocar fallos con mayor facilidad si todos los sitios en los que aparece no se encuentran sincronizados.”

Pero no todos los módulos van ser compuestos con relatividad al viewport, algunos de ellos dependerán de su contenedor, puede que otro módulo en la misma web, y no tenemos las herramientas para hacer que ese módulo sea reutlizable, perdiendo así toda su ventaja como "módulo". No podemos con las herramientas actuales crear un módulo en CSS y marcarnos un DRY en toda regla: Ese componente no se adaptará en muchos casos a todos los posibles contenedores de nuestra web.

El módulo no es modularizable, ¿quién lo modularizará? Aquél que lo modularice buen modularizador será.

Hace poco me pidieron que creara una especie de banner que iba a ser colocado en diferentes sitios de diferentes webs, (podemos pensar en el como un widget), dentro del banner hay campos de formulario, imágenes, botones, etc… y como soy muy vago me dije “Wakkos crea un solo banner, responsive, que se adapte a todas las resoluciones y así puedes jugar al LOL sin que te molesten”, un componente adaptable, que es al fin y al cabo lo que queremos conseguir con todos los componentes que creamos.

El caso es que hice un banner que se adaptaba a todas las resoluciones:

widget-1
Resoluciones Pequeñas
widget-2
Resoluciones menos pequeñas
widget-3
Resoluciones nada pequeñas

Y funcionaba, se adaptaba según la resolución del viewport y era bonito y eché muchas flores en ese espacio infinito donde guardo mi ego. Hasta que intenté colocar el banner en el sidebar de la web y mi ego sufrió un fuerte bajón.

El banner estaba pensado para que funcionara bien en varios tamaños, incluso a 150px de ancho - una resolución difícil de encontrar - pero era imposible detectar el espacio donde desplegaba el banner.

Resolución vs Espacio disponible

Soy un profesional que se dedica a trabajar casi el 100% con CSS, pero sobretodo soy vago y lo que hago me gusta que perdure, que se adapte y que pueda evolucionar sin que yo tenga que dejar de jugar mis partidas al LOL para re-adaptarlo X o Y contenedor (A veces creo que me miran mal cuando paso más tiempo jugando en CodePen probando pequeños trozos de CSS que implementándolos): Estoy probando todos los posibles escenarios para que no me interrumpa mis partidas de LOL si el múdalo o componente cambia de contenedor.

We’re not designing pages. We’re designing systems of components.
Stephen Hay

Cuando creamos componentes, como el banner que yo quería crear, queremos que, al igual que la web en sí, se adapte a cualquier contexto; teníamos media queries para adaptar los elementos de la web y fue lo que usé para el banner de Barkibu, pero no se puede adaptar un componente con mediqueries cuando su comportamiento depende de un contenedor otro que el html y cuando quiero agregar el banner a un sidebar lo puedo adaptar con mediqueries ya que la resolución es de, por ejemplo, 38em pero el sidebar mide 200px (12.5em) de ancho.

.banner {
  background-image: url(#{$image-dir}/howcoolIam.png);
  input {
    width: 100%;
  }
}
@media screen and (min-width: 38em) {
  .banner {
    display: table;
    table-layout: fixed;
    caption-side: top;
    input {
      display: table-cell;
      width: auto;
    }
    h3 {
      display: table-caption;
    }
  }
}

El banner toma las reglas del breakpoint 38em (es la resolución actual) pero su contenedor mide 12.5em con lo que tendríamos un bloque desplegado para 38em en un espacio de 12.5em:

browser-1
Se comporta acatando las reglas del mediquery con el breakpoint a 38em.

Entonces, ¿Cómo solucionamos el problema del mediquery modular?

  • No quiero hacer dos versiones del módulo .banner porque eso no es mantenible y me va a quitar horas de jugar al LOL.
  • No quiero generar mediaqueries con distintos breakpoints para uno u otro caso porque eso no es mantenible y me va a quitar horas de jugar al LOL.

Quiero poder detectar la resolución del contenedor y poder establecer el comportamiento de mi módulo en todos los casos posibles para poder irme a pwnear con Udyr al LOL.

Un módulo o componente no depende de la resolución o viewport en la mayoría de los casos, depende del contenedor y eso no está mal, lo que está mal es que no tengamos un query modular. Sé que suena redundante pero si trabajamos con módulos, ¿Por qué no tenemos una manera modular de establecer comportamientos?

No nos importa cuánto espacio tenemos en el viewport, lo que de verdad nos importa es cuánto espacio tenemos en el contenedor de nuestro módulo o el tamaño del componente en sí, ambas soluciones servirían.

Pero no existen. Queda esperar a ver si la W3C nos proporciona algo - ya estamos discutiendo maneras en las listas de comunicación -.

Mientras tanto

Ya se han hecho Polyfills para intentar ocupar este vacío como el que presenta Tyson Matanich que utiliza lo que considero la sintaxis más adecuada si se llegara a implementar:

  • Con una condición:
    header[min-width~="500px"] {
      background-color: #eee;
    }
  • Con múltiples condiciones:
    header[min-width~="500px"][max-width~="800px"] {
      background-color: #eee;
    }
  • Condicionando un selector hijo
    header[min-width~="500px"] nav {
      background-color: #eee;
    }

Podéis ver ejemplos en CodePen

En el primer caso lo que hacemos es decirle al elemento header que cuando el ancho sea mayor a 500px ajuste la propiedad background-color: #eee.

En el segundo caso vemos como implementar un rango de anchos para establecer reglas: cuando el ancho es igual o mayor a 500px siempre y cuando sea igual o menor a 800px.

El tercer caso es un ejemplo de como seleccionar un elemento hijo nav del selector .banner si el ancho es igual o mayor a 500px.

Esperemos que al igual que Flexbox, podamos contar en breve con Element Queries y poder crear elementos de verdad modulares y adaptables sin polyfills!