Requêtes de conteneur CSS : cas d'utilisation et stratégies de migration
Publié: 2022-03-10Lorsque nous écrivons des requêtes multimédias pour un élément de l'interface utilisateur, nous décrivons toujours le style de cet élément en fonction des dimensions de l'écran. Cette approche fonctionne bien lorsque la réactivité de la requête multimédia de l'élément cible ne doit dépendre que de la taille de la fenêtre d'affichage. Examinons l'exemple de mise en page responsive suivant.
Cependant, la conception Web réactive (RWD) ne se limite pas à une mise en page - les composants individuels de l'interface utilisateur ont généralement des requêtes multimédias qui peuvent changer de style en fonction des dimensions de la fenêtre d'affichage.
Vous avez peut-être déjà remarqué un problème avec la déclaration précédente - la disposition des composants de l'interface utilisateur individuelle ne dépend souvent pas exclusivement des dimensions de la fenêtre. Alors que la mise en page est un élément étroitement lié aux dimensions de la fenêtre d'affichage et l'un des éléments les plus importants du HTML, les composants de l'interface utilisateur peuvent être utilisés dans différents contextes et conteneurs. Si vous y réfléchissez, la fenêtre d'affichage n'est qu'un conteneur et les composants de l'interface utilisateur peuvent être imbriqués dans d'autres conteneurs avec des styles qui affectent les dimensions et la disposition du composant.
Même si le même composant de carte produit est utilisé dans les sections supérieure et inférieure, les styles de composant dépendent non seulement des dimensions de la fenêtre d'affichage, mais également du contexte et des propriétés CSS du conteneur (comme la grille dans l'exemple) où il est placé.
Bien sûr, nous pouvons structurer notre CSS afin de prendre en charge les variations de style pour différents contextes et conteneurs afin de résoudre manuellement le problème de mise en page. Dans le pire des cas, cette variation serait ajoutée avec un remplacement de style, ce qui entraînerait des problèmes de duplication de code et de spécificité.
.product-card { /* Default card style */ } .product-card--narrow { /* Style variation for narrow viewport and containers */ } @media screen and (min-width: 569px) { .product-card--wide { /* Style variation for wider viewport and containers */ } }
Cependant, il s'agit davantage d'une solution de contournement pour les limitations des requêtes multimédias que d'une solution appropriée. Lors de l'écriture de requêtes multimédias pour les éléments de l'interface utilisateur, nous essayons de trouver une valeur de fenêtre « magique » pour un point d'arrêt lorsque l'élément cible a des dimensions minimales où la mise en page ne se brise pas. En bref, nous lions une valeur de dimension de la fenêtre "magique" à la valeur des dimensions de l'élément . Cette valeur est généralement différente de la dimension de la fenêtre et est sujette à des bogues lorsque les dimensions ou la disposition du conteneur intérieur changent.
L'exemple suivant présente ce problème précis : même si un élément de carte de produit réactif a été implémenté et qu'il semble bon dans un cas d'utilisation standard, il semble cassé s'il est déplacé vers un autre conteneur avec des propriétés CSS qui affectent les dimensions de l'élément. Chaque cas d'utilisation supplémentaire nécessite l'ajout de code CSS supplémentaire, ce qui peut entraîner un code dupliqué, un gonflement du code et un code difficile à maintenir.
C'est l'un des problèmes que les requêtes de conteneur tentent de résoudre. Les requêtes de conteneur étendent la fonctionnalité des requêtes multimédia existantes avec des requêtes qui dépendent des dimensions de l'élément cible. Il y a trois avantages majeurs à utiliser cette approche :
- Les styles de requête de conteneur sont appliqués en fonction des dimensions de l'élément cible lui-même. Les composants de l'interface utilisateur pourront s'adapter à n'importe quel contexte ou conteneur donné.
- Les développeurs n'auront pas besoin de rechercher une valeur de dimension de fenêtre d'affichage « nombre magique » qui relie une requête multimédia de fenêtre d'affichage à une dimension cible d'un composant d'interface utilisateur dans un conteneur spécifique ou un contexte spécifique.
- Pas besoin d'ajouter des classes CSS ou des requêtes multimédias supplémentaires pour différents contextes et cas d'utilisation.
"Le site Web réactif idéal est un système de composants flexibles et modulaires qui peuvent être réutilisés pour servir dans plusieurs contextes."
— "Requêtes sur les conteneurs : une fois de plus jusqu'à la brèche", Mat Marquis
Avant de plonger dans les requêtes de conteneur, nous devons vérifier la prise en charge du navigateur et voir comment nous pouvons activer la fonctionnalité expérimentale dans notre navigateur.
Prise en charge du navigateur
Les requêtes de conteneur sont une fonctionnalité expérimentale, actuellement disponible dans la version Chrome Canary au moment de la rédaction de cet article. Si vous souhaitez suivre et exécuter les exemples CodePen de cet article, vous devez activer les requêtes de conteneur dans l'URL des paramètres suivants.
chrome://flags/#enable-container-queries
Si vous utilisez un navigateur qui ne prend pas en charge les requêtes de conteneur, une image présentant l'exemple de travail prévu sera fournie avec la démo CodePen.
Travailler avec des requêtes de conteneur
Les requêtes de conteneur ne sont pas aussi simples que les requêtes de média classiques. Nous devrons ajouter une ligne supplémentaire de code CSS à notre élément d'interface utilisateur pour que les requêtes de conteneur fonctionnent, mais il y a une raison à cela et nous l'aborderons ensuite.
Propriété de confinement
La propriété CSS contain
a été ajoutée à la majorité des navigateurs modernes et a une prise en charge décente de 75% des navigateurs au moment de la rédaction de cet article. La propriété contain
est principalement utilisée pour l'optimisation des performances en indiquant au navigateur quelles parties (sous-arborescences) de la page peuvent être traitées comme indépendantes et n'affecteront pas les modifications apportées aux autres éléments d'une arborescence. De cette façon, si un changement se produit dans un seul élément, le navigateur restituera uniquement cette partie (sous-arborescence) au lieu de la page entière. Avec les valeurs de propriété contain
, nous pouvons spécifier les types de confinement que nous voulons utiliser — layout
, size
ou paint
.
Il existe de nombreux articles intéressants sur la propriété contain
qui décrivent les options disponibles et les cas d'utilisation de manière beaucoup plus détaillée. Je vais donc me concentrer uniquement sur les propriétés liées aux requêtes de conteneur.
Qu'est-ce que la propriété CSS contentment utilisée pour l'optimisation a à voir avec les requêtes de conteneur ? Pour que les requêtes de conteneur fonctionnent, le navigateur doit savoir si un changement se produit dans la disposition des enfants de l'élément et qu'il ne doit restituer que ce composant. Le navigateur saura appliquer le code de la requête de conteneur au composant correspondant lorsque le composant est rendu ou que la dimension du composant change.
Nous utiliserons les style
layout
style pour la contain
, mais nous aurons également besoin d'une valeur supplémentaire qui signale au navigateur l'axe dans lequel le changement se produira.
-
inline-size
Confinement sur l'axe en ligne. On s'attend à ce que cette valeur ait beaucoup plus de cas d'utilisation, elle est donc mise en œuvre en premier. -
block-size
Confinement sur l'axe du bloc. Il est encore en développement et n'est pas disponible actuellement.
Un inconvénient mineur de la propriété contain
est que notre élément de mise en page doit être un enfant d'un élément contain
, ce qui signifie que nous ajoutons un niveau d'imbrication supplémentaire.
<section> <article class="card"> <div class="card__wrapper"> <!-- Card content --> </div> </article> </section>
.card { contain: layout inline-size style; } .card__wrapper { display: grid; grid-gap: 1.5em; grid-template-rows: auto auto; /* ... */ }
Remarquez que nous n'ajoutons pas cette valeur à une section
de type parent plus éloignée et que nous gardons le conteneur aussi proche que possible de l'élément affecté.
"La performance est l'art d'éviter le travail et de rendre tout travail aussi efficace que possible. Dans de nombreux cas, il s'agit de travailler avec le navigateur, pas contre lui.
— "Performances de rendu", Paul Lewis
C'est pourquoi nous devons signaler correctement le navigateur au sujet du changement. Envelopper un élément parent distant avec une propriété contain
peut être contre-productif et affecter négativement les performances de la page. Dans les pires scénarios d'utilisation abusive de la propriété contain
, la mise en page peut même se casser et le navigateur ne la restituera pas correctement.
Requête de conteneur
Une fois la propriété contain
ajoutée au wrapper de l'élément de carte, nous pouvons écrire une requête de conteneur. Nous avons ajouté une propriété contain
à un élément avec la classe card
, nous pouvons donc désormais inclure n'importe lequel de ses éléments enfants dans une requête de conteneur.
Tout comme avec les requêtes média classiques, nous devons définir une requête à l'aide des propriétés min-width
ou max-width
et imbriquer tous les sélecteurs dans le bloc. Cependant, nous utiliserons le mot-clé @container
au lieu de @media
pour définir une requête de conteneur.
@container (min-width: 568px) { .card__wrapper { align-items: center; grid-gap: 1.5em; grid-template-rows: auto; grid-template-columns: 150px auto; } .card__image { min-width: auto; height: auto; } }
Les card__wrapper
et card__image
sont tous deux des enfants de l'élément card
dont la propriété contain
est définie. Lorsque nous remplaçons les requêtes multimédias régulières par des requêtes de conteneur, supprimons les classes CSS supplémentaires pour les conteneurs étroits et exécutons l'exemple CodePen dans un navigateur prenant en charge les requêtes de conteneur, nous obtenons le résultat suivant.
Veuillez noter que les requêtes de conteneur ne s'affichent actuellement pas dans les outils de développement Chrome , ce qui complique un peu le débogage des requêtes de conteneur. Il est prévu que le support de débogage approprié soit ajouté au navigateur à l'avenir.
Vous pouvez voir comment les requêtes de conteneur nous permettent de créer des composants d'interface utilisateur plus robustes et réutilisables qui peuvent s'adapter à pratiquement n'importe quel conteneur et mise en page. Cependant, la prise en charge appropriée des navigateurs pour les requêtes de conteneur est encore loin dans la fonctionnalité. Essayons de voir si nous pouvons implémenter des requêtes de conteneur à l'aide de l'amélioration progressive.
Amélioration progressive et polyfills
Voyons si nous pouvons ajouter une alternative à la variation de classe CSS et aux requêtes multimédias. Nous pouvons utiliser des requêtes de fonctionnalités CSS avec la règle @supports
pour détecter les fonctionnalités de navigateur disponibles. Cependant, nous ne pouvons pas vérifier d'autres requêtes, nous devons donc ajouter une vérification pour une valeur contain: layout inline-size style
. Nous devrons supposer que les navigateurs qui prennent en charge la propriété inline-size
prennent également en charge les requêtes de conteneur.
/* Check if the inline-size value is supported */ @supports (contain: inline-size) { .card { contain: layout inline-size style; } } /* If the inline-size value is not supported, use media query fallback */ @supports not (contain: inline-size) { @media (min-width: 568px) { /* ... */ } } /* Browser ignores @container if it's not supported */ @container (min-width: 568px) { /* Container query styles */ }
Cependant, cette approche peut entraîner des styles dupliqués, car les mêmes styles sont appliqués à la fois par la requête de conteneur et la requête multimédia. Si vous décidez d'implémenter des requêtes de conteneur avec une amélioration progressive, vous souhaiterez utiliser un pré-processeur CSS comme SASS ou un post-processeur comme PostCSS pour éviter de dupliquer des blocs de code et utiliser des mixins CSS ou une autre approche à la place.
Étant donné que cette spécification de requête de conteneur est encore en phase expérimentale, il est important de garder à l'esprit que la spécification ou l'implémentation est susceptible de changer dans les versions futures.
Alternativement, vous pouvez utiliser des polyfills pour fournir une solution de secours fiable. Il y a deux polyfills JavaScript que j'aimerais souligner, qui semblent actuellement être activement maintenus et fournissent les fonctionnalités de requête de conteneur nécessaires :
-
cqfill
de Jonathan Neal
Polyfill JavaScript pour CSS et PostCSS -
react-container-query
par Chris Garcia
Crochet et composant personnalisés pour React
Migration des requêtes de média vers les requêtes de conteneur
Si vous décidez d'implémenter des requêtes de conteneur sur un projet existant qui utilise des requêtes multimédias, vous devrez refactoriser le code HTML et CSS. J'ai trouvé que c'était le moyen le plus rapide et le plus simple d'ajouter des requêtes de conteneur tout en offrant une solution de secours fiable aux requêtes multimédias. Reprenons l'exemple de carte précédent.
<section> <div class="card__wrapper card__wrapper--wide"> <!-- Wide card content --> </div> </section> /* ... */ <aside> <div class="card__wrapper"> <!-- Narrow card content --> </div> </aside>
.card__wrapper { display: grid; grid-gap: 1.5em; grid-template-rows: auto auto; /* ... */ } .card__image { /* ... */ } @media screen and (min-width: 568px) { .card__wrapper--wide { align-items: center; grid-gap: 1.5em; grid-template-rows: auto; grid-template-columns: 150px auto; } .card__image { /* ... */ } }
Tout d'abord, enveloppez l'élément HTML racine auquel une requête multimédia est appliquée avec un élément doté de la propriété contain
.
<section> <article class="card"> <div class="card__wrapper"> <!-- Card content --> </div> </article> </section>
@supports (contain: inline-size) { .card { contain: layout inline-size style; } }
Ensuite, encapsulez une requête multimédia dans une requête de fonctionnalité et ajoutez une requête de conteneur.
@supports not (contain: inline-size) { @media (min-width: 568px) { .card__wrapper--wide { /* ... */ } .card__image { /* ... */ } } } @container (min-width: 568px) { .card__wrapper { /* Same code as .card__wrapper--wide in media query */ } .card__image { /* Same code as .card__image in media query */ } }
Bien que cette méthode entraîne un gonflement du code et du code dupliqué, en utilisant SASS ou PostCSS, vous pouvez éviter de dupliquer le code de développement, de sorte que le code source CSS reste maintenable.
Une fois que les requêtes de conteneur reçoivent une prise en charge appropriée du navigateur, vous pouvez envisager de supprimer les blocs de code @supports not (contain: inline-size)
et continuer à prendre en charge les requêtes de conteneur exclusivement.
Stephanie Eckles a récemment publié un excellent article sur les requêtes de conteneur couvrant diverses stratégies de migration. Je vous recommande de le consulter pour plus d'informations sur le sujet.
Scénarios de cas d'utilisation
Comme nous l'avons vu dans les exemples précédents, les requêtes de conteneur sont mieux utilisées pour les composants hautement réutilisables avec une disposition qui dépend de l'espace de conteneur disponible et qui peuvent être utilisées dans divers contextes et ajoutées à différents conteneurs sur la page.
D'autres exemples incluent (les exemples nécessitent un navigateur prenant en charge les requêtes de conteneur) :
- Composants modulaires tels que cartes, éléments de formulaire, bannières, etc.
- Dispositions adaptables
- Pagination avec différentes fonctionnalités pour mobile et ordinateur
- Expériences amusantes avec le redimensionnement CSS
Conclusion
Une fois la spécification implémentée et largement prise en charge dans les navigateurs, les requêtes de conteneur pourraient devenir une fonctionnalité révolutionnaire. Cela permettra aux développeurs d'écrire des requêtes au niveau des composants, en rapprochant les requêtes des composants associés, au lieu d'utiliser les requêtes multimédias distantes et à peine liées. Cela se traduira par des composants plus robustes, réutilisables et maintenables qui pourront s'adapter à divers cas d'utilisation, dispositions et conteneurs.
Dans l'état actuel des choses, les requêtes de conteneur sont encore dans une phase expérimentale précoce et la mise en œuvre est susceptible de changer. Si vous souhaitez commencer à utiliser des requêtes de conteneur dans vos projets dès aujourd'hui, vous devrez les ajouter à l'aide d'une amélioration progressive avec détection de fonctionnalités ou utiliser un polyfill JavaScript. Les deux cas entraîneront une surcharge dans le code, donc si vous décidez d'utiliser des requêtes de conteneur dans cette phase précoce, assurez-vous de planifier la refactorisation du code une fois que la fonctionnalité sera largement prise en charge.
Les références
- "Requêtes de conteneur : un guide de démarrage rapide" par David A. Herron
- "Dites bonjour aux requêtes de conteneur CSS", Ahmad Shadeed
- "Confinement CSS dans Chrome 52", Paul Lewis
- "Aider les navigateurs à optimiser avec la propriété CSS Contain", Rachel Andrew