Frontend Magento 2

Breakpoint, media query e mixin guard su Magento2

Lettura 6 minuti

La ragione per cui in magento2 bisogna usare una sintassi così lunga per creare una media query (da qui in avanti “mq”):

.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) { … }

al posto della più semplice:

@media screen and (min-width: 1024px) { … }

nativa di css va ricercata nel modo in cui i less vengono assemblati e compilati da Magento2.

Magento, lo dice nella sua documentazione, vuole evitare migliaia di @media query statements sparsi per tutto il codice, ma preferisce raggruppare in un unico punto tutte le regole di una specifica media query. Il che makes sense.

Un po’ come si faceva prima dell’avvento dei preprocessori.

Don’t do this at home

Vi sarà sicuramente capitato di dover aggiungere un breakpoint al vostro layout. Di default Magento predispone questi:

@screen__xxs: 320px;
@screen__xs: 480px;
@screen__s: 640px;
@screen__m: 768px;
@screen__l: 1024px;
@screen__xl: 1440px;

SOURCE: lib/web/css/source/lib/variables/_responsive.less

Supponiamo di voler aggiungere una mq fra l’abisso del @screen__l e del @screen__xl quale per esempio @screen__ml col valore di 1280px (in _variables.less aggiungiamo @screen__ml: 1280px).

Non basta andare nel nostro less e mettere:

.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__ml) { … }

perché questo non è un mixin, ma una specie di IF statement, che nello specifico linguaggio di Less viene chiamato Mixin Guard.

Questa mq così non verrà aggiunta perché questa istruzione è incaricata “soltanto” di verificare una corrispondenza che restituisca true per i due parametri (@extremum e @break) passati e non trovandola non eseguirà le regole contenute.

Allora, presi dallo sconforto, si potrebbe decidere di ricorrere ai metodi tradizionali ed aggiungere le mq nella modalità standard quindi con:

@media (min-width: @screen__ml) { … }

che sì, stavolta aggiunge le nostre regole MA le inserisce nell’ordine sbagliato nella cascata, nello specifico prima di tutte le altre mq generando così conflitti e override di regole CSS.

Per tentare di risolvere il problema si potrebbe allora pensare di correggere solo per quel dato less le altre mq generate col mixin guard di modo che l’elemento interessato all’interno di quel modulo, essendo già nell’ordine corretto nel less, venga restituito nello stesso ordine nel css, evitando così eventuali conflitti.

Quindi supponendo di avere un less con questo flow:

.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) {
   .mini-cart {
      color: red;
     }
}
.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__ml) {
   .mini-cart {
      color: blue;
     }
}

si cambiano entrambe le mixin guard in mq native:

@media all and (min-width: @screen__m), print {
   .mini-cart {
      color: red;
     }
}
@media all and (min-width: @screen__ml), print {
   .mini-cart {
      color: blue;
     }
}

ed ecco che ora la sovrascrittura della nostra regola funziona correttamente.
MA. Ecco cosa succede.

Il css generato ora riporta due volte la regola

@media all and (min-width: 768px), print { … }

perché, come potete immaginare, quella da noi generata con css nativo non è passata dentro al mixin guard che si occupa appunto di raggruppare tutte le mq.

Quindi no. Non è questa la via giusta.

Ma come funziona dunque questo mixin guard? Capirlo is the way perché ci permette a questo punto di non aver più dubbi su come vadano costruite le regole less in Magento secondo le loro linee guida.

Come aggiungere un breakpoint su Magento2

Nella DevDoc di magento c’è un articolo che si intitola pressappoco così e che spiega come aggiungere un breakpoint senza incappare nei problemi di cui sopra quindi ad averlo letto e assimilato si risparmiavano queste prove a tentoni.

  1. Aggiungere in /web/css/source/variables.less il nostro breakpoint:
    @screen__ml: 1280px
  2. Aggiungere in lib/web/css/source/lib/_responsive.less la regola che si occuperà di catturare l’occorrenza del breakpoint (fulcro e passaggio mancante nei nostri precedenti esperimenti a casa)

Ma quello che non spiega, perché giustamente dà per scontato che uno possieda i rudimenti di Less, è come funziona questo mixin guard che all’apparenza sembra soltanto un mixin.

Per cui il punto 1) è chiaro: ci serve una variabile con il nostro nuovo breakpoint e va messa in _variable.less (per una qualche ragione di misunderstanding mia sull’utilizzo di _extend.less l’avevo inizialmente messo in questo less pensando che fosse il posto giusto per aggiungere variabili, ma non veniva letta).

Nel punto 2) veniamo a conoscenza del famoso _responsive.less, fautore di questi magici raggruppamenti, che si presenta così:

//
//  Style groups for 'mobile' devices
//  ---------------------------------------------

& when (@media-target = 'mobile'), (@media-target = 'all') {

   @media only screen and (max-width: (@screen__xxs - 1)) {
       .media-width('max', @screen__xxs);
   }

   @media only screen and (max-width: (@screen__xs - 1)) {
       .media-width('max', @screen__xs);
   }

   @media only screen and (max-width: (@screen__s - 1)) {
       .media-width('max', @screen__s);
   }

   @media only screen and (max-width: (@screen__m - 1)) {
       .media-width('max', @screen__m);
   }

   @media only screen and (max-width: @screen__m) {
       .media-width('max', (@screen__m + 1));
   }

   @media all and (min-width: @screen__s) {
       .media-width('min', @screen__s);
   }

}

//
//  Style groups for 'desktop' devices
//  ---------------------------------------------

& when (@media-target = 'desktop'), (@media-target = 'all') {

   @media all and (min-width: @screen__m),
   print {
       .media-width('min', @screen__m);
   }

   @media all and (min-width: (@screen__m + 1)),
   print {
       .media-width('min', (@screen__m + 1));
   }

   @media all and (min-width: @screen__l),
   print {
       .media-width('min', @screen__l);
   }

   @media all and (min-width: @screen__xl),
   print {
       .media-width('min', @screen__xl);
   }
}

SOURCE: /lib/web/css/source/lib/_responsive.less

Ecco che qui vediamo del codice a noi più familiare, prendiamone un pezzo come esempio:

@media all and (min-width: @screen__xl),
   print {
       .media-width('min', @screen__xl);
   }

@media all and (min-width: @screen__xl) è la media query CSS come la conosciamo

in questo caso per schermi molto grandi (1440px) che al suo interno chiama un mixin al quale vengono passati due argomenti: min e @screen__xl contenente il valore di 1440px.

Ed ecco che qui entrano in gioco le mixin guards!

.media-width('min', @screen__xl) verrà sostituito da tutto il contenuto dei mixin condizionali, chiamiamoli così, che avranno verificato questa specifica condizione, per cui tutti i .media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__xl) {.

Il mixin guard è di più facile comprensione per chi è pratico di programmazione funzionale, ma per chi non lo è, possiamo paragonarlo a un if statement, che, se provassimo a tradurlo in Javascript, risulterebbe così:

function mediaWidth(extremum, break){
  if (extremum == "min" && break == 1440){
   // stile
 }
}

Maggior dettagli qui: https://www.sitepoint.com/understanding-less-guards-loops/.

Per cui…

per aggiungere finalmente il nostro breakpoint, dobbiamo inserire anche qui all’interno del corretto gruppo di stili (desktop -> style-l.css o mobile -> style-m.css) il raggruppamento delle nostre regole per il nuovo breakpoint avendo cura di metterlo nel corretto ordine.

Per il 1280px andrà perciò dopo il 1024 (@screen__l).

//
//  Style groups for 'desktop' devices
//  ---------------------------------------------

& when (@media-target = 'desktop'), (@media-target = 'all') {

   @media all and (min-width: @screen__m),
   print {
       .media-width('min', @screen__m);
   }

   @media all and (min-width: (@screen__m + 1)),
   print {
       .media-width('min', (@screen__m + 1));
   }

   @media all and (min-width: @screen__l),
   print {
       .media-width('min', @screen__l);
   }

   @media all and (min-width: @screen__ml),
   print {
       .media-width('min', @screen__ml);
   }

   @media all and (min-width: @screen__xl),
   print {
       .media-width('min', @screen__xl);
   }
}

E basta mal di pancia!

Avendo ora compreso come funziona si apprezza anche molto di più questa tecnica utilizzata da magento.

Articolo scritto da