Media Queries en Magento 2

Al final hago un TL;DR si quieres saltarte toda la explicación y llegar a la chuleta directamente. La idea es con ejemplos entender cómo se aplican las media queries en Magento 2.

Magento utiliza en su core LessCSS y trae consigo mucho hecho. Como todo framework, nos dice cómo hemos de montar los archivos y nos dice cómo tenemos que aplicar los estilos (para bien o para mal). Una de esas cosas que nos dice cómo hacer, es cómo usar las media queries. No es tan sencillo como parece, ya que incluso aunque queramos usarlo como ya sabemos, veremos que los estilos los aplica por duplicado. ¿POR QUEEEEÉ?

Es en su forma de montar los archivos less y en la funcionalidad que nos puede traer de cabeza…

Punto de partida

Damos por hecho que estamos usando un child theme del Luma y estamos escribiendo estilos en app/design/frontend/Vendor/ChildTheme/web/css/source/_extend.less. Aquí haremos nuestras pruebas.

Veremos el CSS aplicado en el inspector de código de nuestro navegador.

¿Desde donde estirar el hilo para entenderlo? Miremos el <head>

Desde vendor/magento/theme-frontend-blank/web/css/styles-l.less y vendor/magento/theme-frontend-blank/web/css/styles-m.less, ya que son sus respectivos archivos .css del mismo nombre los que estarán cargándose en nuestro Magento en el <head>. Allí vemos dos etiquetas <link>:

<link rel="stylesheet" type="text/css" media="all" href="........styles-m.css">

<link rel="stylesheet" type="text/css" media="screen and (min-width: 768px)" href="........styles-l.css">

El primer truco está en la propiedad «media» utilizada al cargar el archivo styles-l.css.

Primero, styles-l.less está pensado para contener todos los estilos de tablet y escritorio; styles-m.less está pensado para contener estilos de sólo móvil (con una media query) y estilos que son comunes para movil, tablet, escritorio (sin media query aplicada).

Si nos damos cuenta, styles-m.less se carga sin condición ninguna. Pero styles-l.less tiene media=»screen and (min-width: 768px)», por lo que los estilos contenidos en el archivo ya se aplicarán sólo desde 768px en adelante. Realmente dentro de este archivo veremos que aplicamos media queries igualmente dentro del css, pero si hubiera código sin media queries, este código ya por estar en este archivo (que tiene una propiedad ‘media’ en la etiqueta <link>) se aplicaría sólo a desktop y no a mobile.

Esto es importante, ya que en nuestro _extend.less escribiremos estilos que irán a parar automáticamente, en el compilado del less a css, a un archivo o a otro, y dentro o no de una media query.

Vamos a verlo con ejemplos

Si tenemos lo siguiente en nuestro _extend.less y lo analizamos por partes viéndolo en el navegador:

/* PRIMER CASO: SOLO ESTILOS, sin indicar nada. ¡¡Mal!! */
body {
    font-size: 10px !important;
}

/* SEGUNDO CASO: ESTILOS PARA MOBILE -> DESKTOP */
& when (@media-common = true) {
    body {
        color: red !important;
    }
}

/* TERCER CASO: ESTILOS PARA MOBILE SOLO */
.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) {
    body {
        line-height: 1.5em !important;
    }
}
.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__xs) {
    body {
        letter-spacing: 2px !important;
    }
}

/* CUARTO CASO: ESTILOS PARA TABLET EN ADELANTE */
.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) {
    body {
        background-color: #fff !important;
    }
}

/* QUINTO CASO: ESTILOS PARA DESKTOP EN ADELANTE */
.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__l) {
    body {
        font-weight: 400 !important;
    }
}

/* SEXTO CASO: ESTILOS PARA BIGDSKTOP EN ADELANTE */
.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__xl) {
    body {
        font-weight: 600 !important;
    }
}

Primer caso: Sólo los estilos

body {
    font-size: 10px !important;
}

El primer bloque: sin nada más que los estilos. Veremos en el navegador, en el css generado en nuestro body, si filtramos los estilos aplicados por «font-size: 10px !important;» vemos lo siguiente:

Sólo estilos, viendo en desktop

Estilos duplicados en styles-m.css y styles-l.css, y sobreescritos por styles-l.css. Pero eso es porque estamos en escritorio… Si nos vamos a un móvil (pantalla < 768px), ya no está duplicado porque sólo vemos el estilo desde styles-m.css (porque styles-l.css tenía una propiedad media en su <link>… y por debajo de 768px ya no se aplica):

Sólo estilos, viendo en mobile

¿Qué ha pasado?

¿Por qué esta diferencia entre desktop y mobile?

Sin entrar en detalles de implementación (lo entenderemos con los siguientes ejemplos), es porque no le hemos dicho a Magento que estamos escribiendo estilos ni para mobile ni para desktop y el código va a parar a ambos archivos sin ninguna media query.

Para corregir esto, realmente lo que deberíamos haber escrito en nuestro _extend.less sería nuestro siguiente caso.

Segundo caso: Estilos para mobile hasta escritorio

& when (@media-common = true) {
    body {
        color: red !important;
    }
}

Aplicamos una condición con la variable @media-common a true.

Si revisamos de nuevo el navegador tanto en desktop como en mobile, veremos que no hay duplicados. Sólo se cargan estilos desde styles-m.less (para ello filtramos los estilos aplicados al body por color: red !important;)

@media-common=true decide mandar estilos sólo a styles-m.less

Esta es la primera distinción en cuanto a media queries. Con la variable @media-common=true el estilo va a parar sólo a styles-m.less y con esto se aplican los estilos a mobile, tablet, escritorio, etc. Pero, ¿Por qué?

Hemos de mirar en la librería UI de Magento, en lib/web/css/source/lib/_responsive.less donde se define el comportamiento de estos archivos responsive.

Vemos que la variable @media-common la tenemos a true y otra variable que veremos en los siguientes casos (@media-target). Aquí se han definido ambas variables.

Si no le decimos lo contrario, el motor de LESS de magento copiará nuestro contenido de _extend.less y lo repartirá según sus normas entre styles-m.less y styles-l.less, sólo que si no le decimos nada repartirá en ambos lo mismo. Éste es el primer caso «Sólo estilos».

Si le decimos & when (@media-common = true){}, cuando el motor de LESS copia nuestro contenido para repartirlo, sólo va a parar a styles-m.less porque en styles-l.less esta variable se sobreescribe false (si abrimos app/design/frontend/Magento/blank/web/css/styles-l.less en la línea 33 aprox. lo veremos) y por tanto no se copia ese estilo aquí. Éste es el segundo caso «Estilos para mobile hasta escritorio»

Tercer caso: Estilos para mobile sólo

.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) {
    body {
        line-height: 1.5em !important;
    }
}

Si miramos en el navegador, veremos que sólo vemos el estilo aplicado al body (filtrando en el inspector del navegador por line-height: 1.5em !important;) cuando el ancho es <768px y se sirve desde styles-m.less sin encontrar nada en styles-l.less.

Esto se hace con el mixin de LESS .media-width

El mixin .media-width

Para verlo, vamos al archivo de antes lib/web/css/source/lib/_responsive.less donde tenemos definido el mixin para mobile y el mixin para desktop (tiene un comentario que los distingue).

El mixin utiliza 2 variables: @extrenum y @break. Justo debajo, los aplica. Sin entrar en detalles del mixin, simplemente hemos de saber que @extrenum define si nuestra media query usa «max-width» o «min-width» y que @break es el breakpoint de la media query (en lib/web/css/source/lib/variables/_responsive.less las tenemos definidas como variables).

Por tanto, si en nuestro less ponemos .media-width(@extremum, @break) when (@extremum = ‘max’) and (@break = @screen__m), veremos que se compila al css siguiente: @media only screen and (max-width: 767px). Porque nuestro @extrenum era ‘max’ y @break era @screen__m (que vale 768px pero al aplicarlo hace la resta de 1).

Lo mismo podemos hacer con el resto de valores para @break y tendremos otros cortes de media queries.

PERFECTO PERO ¿Por qué en styles-m.css sólo?

La otra variable que se definía en lib/web/css/source/lib/_responsive.less, era @media-target y por defecto es ‘all’.

Pues al copiar el contenido de _extend.less a los respectivos css, en styles-l.less se sobreescribe esta variable a ‘desktop’ (línea 32 aprox) y por ello en lib/web/css/source/lib/_responsive.less no valida el ‘if’.

Ese if que esperaba un ‘mobile’ en lugar de ‘desktop’ y utiliza en los estilos aplicados dentro del ‘if’, las media queries con ‘max-width’. Recordemos que era & when (@media-target = ‘mobile’).

Nota: Lógicamente sí que valida para el siguiente ‘if’ & when (@media-target = ‘desktop’) pero como dentro del ‘if’ los estilos aplicados utilizan ‘min-width’ y no ‘max-width’, no se aplica el mixin aunque entre al fi, y por tanto no hay output al archivo css. Recordemos que nuestro mixin para la media query era .media-width(@extremum, @break) when (@extremum = ‘max’) and (@break = @screen__m). Importante el @extrenum=’max’

Cuarto caso: Estilos para tablet en adelante

.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) {
    body {
        background-color: #fff !important;
    }
}

En este caso es parecido a antes pero con la diferencia de que el mixin que usamos tiene @extremum=’min’, por lo que ocurre que aplica esos mixins (al contrario que antes que aplicada un mixin con @extremum=’max’).

Igual que antes sólo aplica estilos a una de las dos hojas de estilos, por el valor de @media-target, que en este caso es al contrario: para los mixins que aplican ‘min’ en @extremum vale ‘desktop’ y eso solo pondrá estilos en styles-l.less.

El resultado es que sólo tenemos ese estilo (podemos filtrar los estilos aplicados al body por background-color: #fff !important;) sólo está en styles-l.css y además en una media query con ‘min-width’ y además que esta hoja de estilos ya se aplica únicamente cuando se cumple la propiedad media de su etiqueta <link> en el head.

Quinto caso y sexto: Estilos para desktop y big desktop, en adelante

 .media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__l) {
     body {
         font-weight: 400 !important;
     }
 }
.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__xl) {
     body {
         font-weight: 600 !important;
     }
 }

Exactamente igual que el caso anterior, pero con valores distintos para @break.

TL;DR

Chuleta para aplicar estilos con media queries:

/* En el head: */
<link rel="stylesheet" type="text/css" media="all" href="...styles-m.css">
<link rel="stylesheet" type="text/css" media="screen and (min-width: 768px)" href="...styles-l.css">

/* Estos styles-l.less y styles-m.less dan valores a la variables @media-common and @media-target */

/* En lib/web/css/source/lib/variables/_responsive.less se definen las variables de anchos de pantalla para usar en los mixins --*/
@screen__xxs: 320px;
@screen__xs: 480px;
@screen__s: 640px;
@screen__m: 768px;
@screen__l: 1024px;
@screen__xl: 1440px;


/* En app/design/frontend/Vendor/Theme/web/css/source/_extend.less */
/* PRIMER CASO: SOLO ESTILOS, sin indicar nada. ¡¡Mal!! */
body {
    font-size: 10px !important;
}

/* SEGUNDO CASO: ESTILOS PARA MOBILE -> DESKTOP */
& when (@media-common = true) {
    body {
        color: red !important;
    }
}

/* TERCER CASO: ESTILOS PARA MOBILE SOLO */
.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) {
    body {
        line-height: 1.5em !important;
    }
}
.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__xs) {
    body {
        letter-spacing: 2px !important;
    }
}

/* CUARTO CASO: ESTILOS PARA TABLET EN ADELANTE */
.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) {
    body {
        background-color: #fff !important;
    }
}

/* QUINTO CASO: ESTILOS PARA DESKTOP EN ADELANTE */
.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__l) {
    body {
        font-weight: 400 !important;
    }
}

/* SEXTO CASO: ESTILOS PARA BIGDSKTOP EN ADELANTE */
.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__xl) {
    body {
        font-weight: 600 !important;
    }
}