Des composants responsives

sans media-queries

Benoist LAWNICZAK

@PunkAdelistic

https://slides.com/benoist/no-mq/

"C'est quoi le problème avec les media-queries ?"

La solution "classique"

.card {
  /* définition de notre card "empilée" - version par défaut */
}



@media (min-width: 50em) {
  .card-featured {
    /* la version "horizontale" */
    display: flex;
    ...
  }
}

"Il y a une solution ?"

"Oui, les container queries !"

Tester les container queries

Dans Chrome, rendez-vous à l'adresse : chrome://flags

Cas pratique

<div class="card-container">
  <article class="card">
    <div class="card-img">
      <img src="path/to/my/image" alt="Lorem ipsum sit dolor amet">
    </div>
    <div class="card-content">
      <h3 class="card-title">Lorem, ipsum dolor.</h3>
      <p class="card-desc">Lorem ipsum dolor sit amet consectetur adipisicing elit. Numquam iure asperiores totam officiis nulla iste nihil dolorem commodi enim.</p>
      <a href="#" class="card-link">Lire la suite</a>
    </div>
  </article>
</div>
.card {
  display: flex;
  flex-direction: column;
  /* ... */
}

.card-img img {
  display: block;
  width: 100%;
  height: 100%;
  aspect-ratio: 21 / 9;
  object-fit: cover;
  object-position: center;
}

/* ... */
.card-container {
  container-type: inline-size;
}
@container (min-width: 37em) {
  .card { flex-direction: row; }
  
  .card-img {
    position: relative;
    flex: 1 1 33%;
  }
  
  .card-img img {
    position: absolute;
    inset: 0;    
  }
  
  .card-content { flex: 1 1 67%; }
}
@container (min-width: 45em) {
  .card { flex-direction: row-reverse; }
}

@container (min-width: 56em) {
  .card {
    position: relative;
    font-size: 1.25em;
  }
  
  .card-img {
    position: absolute;
    inset: 0;
  }
  
  .card-content {
    flex: 1 1 100%;
    /* ... */
  }
}

"C'est bien beau,

mais aujourd'hui,

quelle solution ? "

Il existe un polyfill :

https://github.com/jsxtools/cqfill

On peut utiliser l'API ResizeObserver

"Et avec du CSS seulement ? "

Flexbox et la méthode

"The Flexbox Holy Albatross Reincarnated"

(Merci Heydon Pickering !)

Le concept

.card {
  display: flex;
  flex-wrap: wrap;
}
.card-img, .card-content {
  flex-grow: 1;
  flex-basis: calc(calc(40em - 100%) * 999); 
  /* (point de rupture à 40em - la largeur du composant) * 999 */
}

/* 
Cas 1 : container >= 40em : valeur négative = 0 -> les éléments se mettent en ligne
Cas 2 : container < 40em : valeur positive élevée = 100% -> les éléments s'empilent 
*/

Le concept

Amélioration avec les custom properties

:root {
  --breakpoint: calc(45em - 100%);
  --ratio-img: 1;     /* 1/3 */
  --ratio-content: 2; /* 2/3 */
}

.card-img {
  flex-grow: var(--ratio-img);
  flex-basis: calc(var(--breakpoint) * 999);
}

.card-content {
  flex-grow: var(--ratio-content);
  flex-basis: calc(var(--breakpoint) * 999);
}
.card-alternative {
  --breakpoint: calc(36em - 100%);
  --ratio-img : 2;    /* 2/5 */
  --ratio-content: 3; /* 3/5 */
}
<article class="card" style="--breakpoint: calc(36em - 100%); --ratio-img: 2; --ratio-content: 3;">

Gestion de l'image

.card-img {
  position: relative;
  padding-top: calc(100% * 9 / 21); 
  /* Les % des paddings se calculent par rapport à la largeur de l'élément parent */
  /* Ici, on obtient donc un padding-top égal à 9/21 de la largeur du parent */
}

.card-img img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

Problème : c'est bon pour la card empilée,

mais c'est un peu haut pour la card en ligne !

On améliore

.card-img {
  position: relative;
  --ratio-img-w: 21;
  --ratio-img-h: 9;
}

.card-img::before {
  content: '';
  display: block;
  padding-top: calc(100% * var(--ratio-img-h) / var(--ratio-img-w));
  /* Le parent, c'est .card-img ! Problème réglé. */
}

La démo

On ne peut pas aller aussi loin qu'avec les container queries,

mais au moins, c'est utilisable en production !

"Et pour le layout ?

On peut sans media queries ?"

.grid {
  display: grid;
  grid-template-columns: 25% 50% 25%;
  /* 3 colonnes avec chacune leur propre taille */
  /* Mais si je mets de l'espace entre les colonnes, mon calcul est faux */
}
.grid {
  display: grid;
  grid-template-columns: 1fr 2fr 1fr;
  /* L'unité fr correspond à des fractions de l'espace disponible */
  gap: 2em 1em;
  /* Pas besoin de marges ou paddings, la propriété gap sert à créer des gouttières */
  /* (ici 2em en vertical, 1em en horizontal)
}

Une grille automatique

.grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  /* 4 colonnes de même largeur */
}
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(18em, 1fr));
  /* Autant de colonnes que possible dans l'espace disponible */
  /* Chaque colonne fait au minimum 18em de large */
}

Un élément qui prend toute la largeur de la grille

<div class="grid">
  <div class="grid-item">...</div>
  <div class="grid-item">...</div>
  <div class="grid-item">...</div>
  <div class="grid-item">...</div>
  <div class="grid-item">...</div>
</div>
.grid-item:first-child {
  grid-column: 1 / -1; /* De la première ligne à la dernière */
}

"On peut éviter l'étape 3 colonnes avec un élément tout seul ?"

Les fonctions mathématiques de CSS

support 90% (https://caniuse.com/css-math-functions)

/* La fonction min() */
.card {
  width: min(50%, 40em);
  /* la largeur sera le plus petit des deux - soit 50% du parent, soit 40em */
  /* équivalent à : */
  width: 50%;
  max-width: 40em;
}

"On peut éviter l'étape 3 colonnes avec un élément tout seul ?"

/* La fonction max() */
.card {
  width: max(50%, 40em);
  /* la largeur sera le plus grand des deux - soit 50% du parent, soit 40em */
  /* équivalent à : */
  width: 50%;
  min-width: 40em;
}

Les fonctions mathématiques de CSS

support 90% (https://caniuse.com/css-math-functions)

"On peut éviter l'étape 3 colonnes avec un élément tout seul ?"

/* La fonction clamp() */
.card {
  width: clamp(20em, 50%, 40em);
  /* la largeur sera de 50% de celle du parent avec un minimum de 20em et un maximum de 40em */
  /* équivalent à : */
  width: 50%;
  min-width: 20em;
  max-width: 40em;
}

/* Une typographie fluide */
p {
  font-size: clamp(0.8em, 2vw, 1.4em);
  /* La taille de police sera de 2vw avec un minimum de 0.8em et un maximum de 1.4em */
}

Etape 1 : de 2 à 1 colonnes

:root {
  --breakpoint: 37em;
  --columns-number: 2;
}

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill,
    minmax(
      /* valeur minimum de minmax */
      clamp(
        /* valeur minimum de clamp */
        100% / (var(--columns-number) + 1) + 0.1%,
        /* valeur souhaitée de clamp */
        (var(--breakpoint) - 100vw)*1000,
        /* valeur maximum de clamp */
        100%
      )
    /* valeur maximale de minmax */
    ,1fr));
}

Etape 2 : de 4 à 2 à 1 colonnes

:root {
  --breakpoint-1: 66em;
  --columns-breakpoint-1: 4;
  --breakpoint-2: 37em;
  --columns-breakpoint-2: 2;
}

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill,
    minmax(
      clamp(
        clamp(
          100% / (var(--columns-breakpoint-1) + 1) + 0.1%, 
          (var(--breakpoint-1) - 100vw) * 1000, 
          100% / (var(--columns-breakpoint-2) + 1) + 0.1%
        ), 
        (var(--breakpoint-2) - 100vw) * 1000,
        100%
      ),
      1fr)
    );
}

Condition

Fonction mathématique

Variable

Le CSS, un langage de programmation ?

(Sinon la démo, c'est ici : https://codepen.io/cefim/pen/NWpzRbw )

DSL (Domain Specific Language) vs GPL (General Purpose Language)

https://css-tricks.com/is-css-a-programming-language/

Sources

Des questions ?

 

(Et merci)

Des composants responsives sans media queries

By Benoist Lawniczak

Des composants responsives sans media queries

AFUP Day - Tours - 28 mai 2021

  • 526