Requêtes de conteneur CSS : cas d'utilisation et stratégies de migration

Publié: 2022-03-10
Résumé rapide ↬ Les requêtes CSS Container rapprochent les requêtes multimédias des éléments cibles eux-mêmes et leur permettent de s'adapter à pratiquement n'importe quel conteneur ou mise en page donné. Dans cet article, nous allons couvrir les bases des requêtes de conteneur CSS et comment les utiliser aujourd'hui avec l'amélioration progressive ou les polyfills.

Lorsque 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.

Exemple de mise en page responsive. L'image de gauche montre la disposition du bureau à une largeur de fenêtre de 1280 pixels et l'image de droite montre la disposition mobile à une largeur de fenêtre de 568 pixels.
Exemple de mise en page responsive. L'image de gauche montre la disposition du bureau à une largeur de fenêtre de 1280 pixels et l'image de droite montre la disposition mobile à une largeur de fenêtre de 568 pixels. ( Grand aperçu )

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.

Exemple de composant de fiche produit responsive. L'image de gauche montre un composant à une largeur de fenêtre de 720 pixels et l'image de droite montre la disposition des composants à une largeur de fenêtre de 568 pixels.
Exemple de composant de fiche produit responsive. L'image de gauche montre un composant à une largeur de fenêtre de 720 pixels et l'image de droite montre la disposition des composants à une largeur de fenêtre de 568 pixels. ( Grand aperçu )

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.

Exemple de mise en page avec le même composant d'interface utilisateur de fiche produit dans la grille à 3 colonnes de la section supérieure et la liste de la section inférieure.
Exemple de mise en page avec le même composant d'interface utilisateur de fiche produit dans la grille à 3 colonnes de la section supérieure et la liste de la section inférieure. ( Grand aperçu )

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.

Exemple de la façon dont la requête multimédia ne peut pas être liée de manière fiable aux dimensions de l'élément. Diverses propriétés CSS peuvent affecter les dimensions des éléments dans un conteneur. Dans cet exemple, le rembourrage du conteneur est différent entre les deux images.
Exemple de la façon dont la requête multimédia ne peut pas être liée de manière fiable aux dimensions de l'élément. Diverses propriétés CSS peuvent affecter les dimensions des éléments dans un conteneur. Dans cet exemple, le rembourrage du conteneur est différent entre les deux images. ( Grand aperçu )

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.

Voir le Pen [Product Cards: Various Containers](https://codepen.io/smashingmag/pen/eYvWVxz) par Adrian Bece.

Voir les cartes de produit Pen : Divers conteneurs par Adrian Bece.

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
Requêtes de conteneur
( Grand aperçu )

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.

Plus après saut! Continuez à lire ci-dessous ↓

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.

Dans cet exemple, nous ne redimensionnons pas la fenêtre, mais l'élément conteneur <section> lui-même auquel la propriété CSS resize est appliquée. Le composant bascule automatiquement entre les mises en page en fonction des dimensions du conteneur.
Dans cet exemple, nous ne redimensionnons pas la fenêtre, mais l'élément conteneur <section> lui-même auquel la propriété CSS resize est appliquée. Le composant bascule automatiquement entre les mises en page en fonction des dimensions du conteneur. ( Grand aperçu )

Voir Pen [Product Cards: Container Queries](https://codepen.io/smashingmag/pen/PopmQLV) par Adrian Bece.

Voir les fiches produit Pen : Requêtes sur les conteneurs par Adrian Bece.

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.

Voir le Pen [Product Cards: Container Queries with progressive enhancement](https://codepen.io/smashingmag/pen/zYZwRXZ) par Adrian Bece.

Voir les fiches produit Pen : Requêtes de conteneur avec amélioration progressive par Adrian Bece.

É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