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 :
On peut utiliser l'API ResizeObserver
"Et avec du CSS seulement ? "
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)
Sources
Container queries
- https://www.miriamsuzanne.com/2021/05/02/container-queries/
- https://ishadeed.com/article/say-hello-to-css-container-queries/
Composants responsives sans media queries
- https://heydonworks.com/article/the-flexbox-holy-albatross-reincarnated/
- https://css-tricks.com/how-to-make-a-media-query-less-card-component/
Layout sans media queries
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
- 519