Fermer

juin 9, 2021

Rencontrez :has, un sélecteur de parent CSS natif (et plus) —


À propos de l'auteur

Adrian Bece est un développeur Web polyvalent avec une vaste expérience du commerce électronique qui travaille actuellement chez PROTOTYP en tant que responsable technique. Il apprécie …
En savoir plus sur
Adrien

Qu'est-ce qui fait du sélecteur relationnel l'une des fonctionnalités les plus demandées et comment, en tant que développeurs, faisons-nous pour éviter de l'avoir ? Dans cet article, nous allons vérifier les premières spécifications du sélecteur :has et voir comment il devrait améliorer le flux de travail CSS une fois publié.

Le sélecteur parent a été sur la liste de souhaits des développeurs pour plus de 10 ans et depuis, il est devenu l'une des fonctionnalités CSS les plus demandées avec les requêtes de conteneur. La principale raison pour laquelle cette fonctionnalité n'a pas été implémentée pendant tout ce temps semble être due à des problèmes de performances. La même chose a été dite à propos des requêtes de conteneurs et celles-ci sont actuellement ajoutées aux versions bêta des navigateurs, de sorte que ces performances ne semblent plus être un problème.

Les moteurs de rendu des navigateurs se sont considérablement améliorés depuis ensuite. Le processus de rendu a été optimisé au point que les navigateurs peuvent déterminer efficacement ce qui doit être rendu ou mis à jour et ce qui ne l'est pas, ouvrant la voie à un nouvel ensemble de fonctionnalités intéressantes.

Brian Kandell a récemment annoncé. que son équipe d'Igalia est en train de prototyper un sélecteur :has qui servira de sélecteur parent, mais il pourrait avoir une gamme beaucoup plus large de cas d'utilisation au-delà. La communauté des développeurs l'appelle "sélecteur parent" et certains développeurs ont souligné que le nom n'est pas très précis. Un nom plus approprié serait un sélecteur relationnel ou une pseudo-classe relationnelle selon la spécificationdonc je ferai référence à :has en tant que tel à partir de maintenant dans l'article.[19659006]L'équipe d'Igalia a travaillé sur certaines fonctionnalités de moteur Web notables telles que Grille CSS et requêtes de conteneuril y a donc une chance pour :has sélecteur de voir le lumière du jour, mais il reste encore un long chemin à parcourir.

Qu'est-ce qui fait du sélecteur relationnel l'une des fonctionnalités les plus demandées ces dernières années et comment les développeurs travaillent-ils autour du sélecteur manquant ? Dans cet article, nous allons répondre à ces questions et consulter les premières spécifications de  :has sélecteur et voir comment il devrait améliorer le flux de travail de style une fois publié.

Utilisation potentielle- Cas

Le sélecteur relationnel serait utile pour l'application conditionnelle de styles aux composants de l'interface utilisateur en fonction du contenu ou de l'état de ses enfants ou de ses éléments suivants dans une arborescence DOM. Le prototype de sélecteur relationnel à venir pourrait étendre la gamme et les cas d'utilisation des sélecteurs existants, améliorer la qualité et la robustesse du CSS et réduire le besoin d'utiliser JavaScript pour appliquer des styles et des classes CSS à ces cas d'utilisation.

Jetons un coup d'œil à quelques exemples spécifiques pour nous aider à illustrer la variété des cas d'utilisation potentiels.

Variations basées sur le contenu

Certains éléments de l'interface utilisateur peuvent avoir plusieurs variations en fonction de divers aspects – contenu, emplacement sur la page , état enfant, etc. Dans ces cas, nous créons généralement plusieurs classes CSS pour couvrir toutes les variantes possibles et les appliquons manuellement ou avec JavaScript, selon l'approche et la pile technologique.

Quatre variantes de carte selon le contenu. La disposition du conteneur de carte parent dépend du contenu et le style du conteneur d'image de carte est différent selon que le conteneur d'image a une légende d'image présente ou non.
Quatre variantes de carte selon le contenu. La disposition du conteneur de carte parent dépend du contenu et le style du conteneur d'image de carte est différent selon que le conteneur d'image a une légende d'image présente ou non. ( Grand aperçu)

Même en utilisant une méthodologie de nommage CSS comme BEMles développeurs doivent garder une trace des différentes classes CSS et s'assurer de les appliquer correctement à l'élément parent et , éventuellement, aux éléments enfants concernés. Selon le nombre de variantes, les styles de composants peuvent rapidement devenir incontrôlables et devenir difficiles à gérer et à maintenir, ce qui entraîne des bogues. Les développeurs devraient donc documenter toutes les variantes et cas d'utilisation en utilisant des outils tels que Storybook.

Avec un sélecteur CSS relationnel, les développeurs pourraient écrire des vérifications de contenu directement dans CSS et les styles seraient appliqués automatiquement. Cela réduirait le nombre de classes CSS de variation, diminuerait la possibilité de bogues causés par une erreur humaine, et les sélecteurs seraient auto-documentés avec les vérifications de condition.

Styles basés sur la validation

CSS prend en charge les pseudo-classes d'entrée comme :valid et :invalid pour cibler les éléments qui sont validés avec succès et les éléments qui ne sont pas validés, respectueusement. Combiné avec des attributs d'entrée HTML tels que pattern et requiredil permet la validation de formulaire natif sans avoir besoin de s'appuyer sur JavaScript.

Cependant, les éléments de ciblage avec  :valid et :invalid est limité au ciblage de l'élément lui-même ou de son élément adjacent. Selon la conception et la structure HTML, les éléments du conteneur d'entrée ou les éléments précédents tels que les éléments label nécessitent également un certain style pour être appliqués.

Les conteneurs d'entrée d'e-mail individuels changent de style en fonction de la validité de l'état d'entrée de l'e-mail. Lorsque toutes les entrées sont validées avec succès, le bouton d'envoi (situé juste après le conteneur d'entrée final dans le DOM) est activé.
Les conteneurs d'entrée d'e-mail individuels changent de style en fonction de la validité de l'état d'entrée d'e-mail. Lorsque toutes les entrées sont validées avec succès, le bouton de soumission (situé juste après le conteneur d'entrée final dans le DOM) est activé. ( Grand aperçu)

Le sélecteur relationnel étendrait le cas d'utilisation pour les pseudo-classes d'état d'entrée comme :valid et :invalid en autorisant le élément parent ou éléments précédents à styliser en fonction de la validité d'entrée.

Cela ne s'applique pas uniquement à ces pseudo-classes. Lorsque vous travaillez avec une API externe qui renvoie des messages d'erreur qui sont ajoutés dans le conteneur d'entrée, il ne sera pas nécessaire d'appliquer également la classe CSS appropriée au conteneur. En écrivant un sélecteur relationnel avec une condition qui vérifie si le conteneur de messages enfant est vide, les styles appropriés peuvent être appliqués au conteneur.

Children Element State

]Parfois, un élément parent ou les styles d'éléments précédents dépendent de l'état d'un élément cible. Ce cas est différent de l'état de validation car l'état n'est pas étroitement lié à la validité de l'entrée.

Comme dans l'exemple précédent, :checked pseudo-classe pour les éléments de case à cocher et d'entrée radio se limite au ciblage de l'élément lui-même ou de son élément adjacent. Ceci n'est pas limité aux pseudo-classes comme :checked:disabled:hover:visitedetc. mais à n'importe quoi autrement que les sélecteurs CSS peuvent cibler, comme la disponibilité d'un élément, d'un attribut, d'une classe CSS, d'un identifiant, etc. spécifiques.

Les sélecteurs de relation étendraient la plage et les cas d'utilisation des sélecteurs CSS au-delà de l'élément affecté ou de son élément adjacent.

 Lorsqu'une ou plusieurs cases à cocher dans la liste déroulante des filtres sont cochées, le bouton modifie l'icône pour indiquer l'état du filtre actif dans le groupe.
Lorsqu'une ou plusieurs cases à cocher dans la liste déroulante des filtres sont cochées, le bouton modifie l'icône pour indiquer le état du filtre actif dans le groupe. ( Grand aperçu)

Sélection des frères et sœurs précédents

Les sélecteurs CSS sont limités par la direction de sélection — l'élément descendant ou suivant peut être sélectionné, mais pas l'élément parent ou précédent.

Un élément relationnel Le sélecteur peut également être utilisé comme sélecteur de frère précédent.

Exemple d'entrée d'étiquette flottante à partir de l'interface utilisateur Google Material. L'étiquette (frère précédent) est stylisée en fonction du focus et de l'état de la valeur de l'entrée.
Exemple d'entrée d'étiquette flottante de l'interface utilisateur Google Material. L'étiquette (frère précédent) est stylisée en fonction du focus et de l'état de la valeur de l'entrée. ( Grand aperçu)

Avancé :empty Sélecteur

Lorsque vous travaillez avec des éléments chargés dynamiquement et que vous utilisez des chargeurs squelettes, il est courant de basculer une classe CSS de chargement sur l'élément parent avec JavaScript une fois que les données sont récupérées et que les composants sont remplis de données.

Le sélecteur relationnel pourrait éliminer le besoin de la fonction de bascule de classe CSS JavaScript en étendant la plage et les fonctionnalités de la pseudo-classe :empty. Avec le sélecteur relationnel, les conditions nécessaires pour afficher un élément peuvent être définies dans CSS en ciblant les éléments HTML de données requis et en vérifiant s'ils sont remplis de données. Cette approche devrait fonctionner avec des éléments complexes et profondément imbriqués.

 L'espace réservé squelette est affiché sur le parent jusqu'à ce que tous les conteneurs de données (éléments de paragraphe profondément imbriqués) soient remplis de données.
L'espace réservé squelette est affiché sur le parent jusqu'à ce que tous les conteneurs de données éléments de paragraphe imbriqués) sont renseignés avec des données. ( Grand aperçu)

CSS :a spécification de pseudo-classe

Gardez à l'esprit que :has n'est pas pris en charge dans les navigateurs donc les extraits de code liés à la pseudo-classe à venir ne fonctionneront pas. La pseudo-classe relationnelle est définie dans la spécification de niveau 4 des sélecteurs qui a été mise à jour depuis sa version initiale en 2011, de sorte que la spécification est déjà bien définie et prête pour le prototypage et le développement.

Cela étant dit, plongeons-nous dans :a spécification de pseudo-classe. L'idée derrière la pseudo-classe est d'appliquer des styles à un sélecteur si la condition (définie comme un sélecteur CSS normal) a été remplie.

/* Sélectionnez les éléments de figure qui ont une légende comme élément enfant */
figure:ha(figcaption) { /* ... */ }

/* Sélectionnez les éléments de bouton qui ont un élément avec la classe .icon en tant qu'enfant */
bouton:a(.icon) { /* ... */ }

/* Sélectionnez les éléments d'article qui ont un élément h2 suivi d'un élément de paragraphe */
article:a(h2 + p) { /* ... */ }

Semblable aux autres pseudo-classes comme :notle pseudo sélecteur relationnel se compose des parties suivantes.

:has() { /* ... */ }

  • Sélecteur d'un élément qui sera ciblé si la condition passée en argument à la pseudo-classe :has est remplie. Le sélecteur de condition est limité à cet élément.

  • Une condition définie avec un sélecteur CSS qui doit être remplie pour que les styles soient appliqués au sélecteur.

Comme avec la plupart des pseudo-classes, les sélecteurs peuvent être chaînés pour cibler les éléments enfants. d'un élément cible ou d'un élément adjacent.

/* Sélectionnez un élément d'image qui est un enfant d'un élément de figure si l'élément de figure a une légende en tant qu'enfant */
figure:has(figcaption) img { /* ... */ }

/* Sélectionnez un élément de bouton qui est un enfant d'un élément de formulaire si un élément d'entrée de case à cocher enfant est coché */
form:has(input[type="checkbox"]:checked) bouton { /* ... */ }

À partir de ces quelques exemples, il est évident à quel point la pseudo-classe :has est polyvalente, puissante et utile. Elle peut même être combinée avec d'autres pseudo-classes comme :not pour créer des sélecteurs relationnels complexes.

/* Sélectionner des éléments de carte qui n'ont pas d'éléments vides */
.card:not(:has(*:empty)) { /* ... */ }

/* Sélectionnez l'élément de formulaire où au moins une case à cocher n'est pas cochée */
form:has(input[type="checkbox"]:not(:checked)) { /* ... */ }

Le sélecteur relationnel n'est pas limité au contenu et à l'état des enfants de l'élément cible, mais peut également cibler des éléments adjacents dans l'arborescence DOM, ce qui en fait un « sélecteur frère précédent ».

/* Sélectionnez les éléments de paragraphe qui sont suivis. par un élément d'image */
p:has(+img) { /* ... */ }

/* Sélectionnez les éléments d'image qui sont suivis par l'élément figcaption qui n'a pas de classe "cachée" appliquée */
img:has(~figcaption:not(.hidden)) { /* ... */ }

/* Sélectionnez les éléments d'étiquette qui sont suivis d'un élément d'entrée qui n'est pas actif */
label:has(~input:not(:focus)) { /* ... */ }

En un mot, le sélecteur relationnel ancre la sélection CSS à un élément avec :has pseudo-class et empêche la sélection de se déplacer vers les éléments qui sont passés en argument au pseudo-class.

.card .title .icon -> l'élément .icon est sélectionné
.card:has(.title .icon) -> l'élément .card est sélectionné

.image + .caption -> l'élément .caption est sélectionné
.image:has(+.caption) -> L'élément .image est sélectionné

Approche actuelle et solutions de contournement

Les développeurs doivent actuellement utiliser diverses solutions de contournement pour compenser le sélecteur relationnel manquant. Indépendamment des solutions de contournement et comme discuté dans cet article, il est évident à quel point le sélecteur relationnel aurait un impact et changerait la donne une fois publié.

Dans cet article, nous couvrirons les deux approches les plus utilisées pour traiter les cas d'utilisation. où le sélecteur relationnel serait idéal :

  • Classes de variation CSS.
  • Solution JavaScript et implémentation jQuery de la pseudo-classe  :has.

Classes de variation CSS pour les éléments statiques

Avec variation CSS classes (modifier classes dans BEM), les développeurs peuvent attribuer manuellement une classe CSS appropriée aux éléments en fonction du contenu de l'élément. Cette approche fonctionne pour les éléments statiques dont le contenu ou l'état ne changera pas après le rendu initial.

Jetons un coup d'œil à l'exemple de composant de carte suivant qui présente plusieurs variantes en fonction du contenu. Certaines cartes n'ont pas d'image, d'autres n'ont pas de description et une carte a une légende sur l'image.

Voir le stylo [Card variations](https://codepen.io/smashingmag/pen/jOBpeQo) par Adrian Bece.

Voir le stylo Variations de carte par Adrian Bece.

Pour que ces cartes aient la bonne mise en page, les développeurs doivent appliquer les classes CSS de modificateur correctes manuellement. En fonction de la conception, les éléments peuvent avoir de multiples variations, ce qui entraîne un grand nombre de classes de modificateurs, ce qui conduit parfois à des solutions HTML créatives pour regrouper toutes ces classes dans le balisage. Les développeurs doivent garder une trace des classes CSS, maintenir la documentation et s'assurer d'appliquer les classes appropriées.

.card { /* ... */}
.card--news { /* ... */ }
.card--text { /* ... */ }
.card--featured { /* ... */ }

.card__title { /* ... */ }
.card__title--news { /* ... */ }
.card__title--text { /* ... */ }

/* ... */

Contournement JavaScript

Pour les cas plus complexes, lorsque le style de l'élément parent appliqué dépend de l'état enfant ou du contenu de l'élément qui change de manière dynamique, les développeurs utilisent JavaScript pour appliquer les styles nécessaires à l'élément parent en fonction des modifications apportées au contenu ou à l'état . Ceci est généralement géré en écrivant une solution personnalisée au cas par cas.

Voir le stylo [Filter button state](https://codepen.io/smashingmag/pen/LYWBgXy) par Adrian Bece.

Voir le Pen Filter button state par Adrian Bece.

Relational Selector In jQuery

Implementation of relational :has selector has existait dans la bibliothèque JavaScript populaire jQuery depuis 2007 et suit la spécification CSS. Bien sûr, la principale limitation de cette implémentation est que la ligne jQuery doit être jointe manuellement à l'intérieur d'un écouteur d'événement, alors que l'implémentation CSS native ferait partie du processus de rendu du navigateur et répondrait automatiquement à l'état de la page et aux changements de contenu.[19659006] L'inconvénient de l'approche JavaScript et jQuery est, bien sûr, la dépendance à l'égard des bibliothèques JavaScript et jQuery, ce qui peut augmenter la taille globale de la page et le temps d'analyse JavaScript. De plus, les utilisateurs qui naviguent sur le Web avec JavaScript désactivé rencontreront des bogues visuels si le repli n'est pas implémenté.

// Cela ne s'exécute que lors du rendu initial.
$("button:has(+.filters input:checked)").addClass("button--active");

// Cela s'exécute à chaque fois que l'on clique sur l'entrée
$("entrée").click(function() {
  $("bouton").removeClass("surligner");
  $("button:has(+.filters input:checked)").addClass("highlight");
})

Voir le stylet [Email inputs — valid / invalid](https://codepen.io/smashingmag/pen/BaWPqqO) par Adrian Bece.

Voir le stylet Entrées d'e-mail — valides/invalides par Adrian Bece.

Conclusion

Semblable aux requêtes de conteneur, :has pseudo-classe changera la donne lorsqu'elle sera implémentée dans les navigateurs. Le sélecteur relationnel permettra aux développeurs d'écrire des sélecteurs puissants et polyvalents qui ne sont actuellement pas possibles avec CSS.

Aujourd'hui, les développeurs traitent la fonctionnalité de sélecteur parent manquante en écrivant plusieurs classes CSS de modificateur qui devaient être appliquées manuellement ou avec JavaScript, si le sélecteur dépend d'un état d'élément enfant. Le sélecteur relationnel devrait réduire la quantité de classes CSS de modificateur en permettant aux développeurs d'écrire des sélecteurs robustes auto-documentés, et devrait réduire le besoin pour JavaScript d'appliquer des styles dynamiques.

Pouvez-vous penser à d'autres exemples où le sélecteur parent ou le sélecteur frère précédent serait utile ? Utilisez-vous une solution de contournement différente pour gérer le sélecteur relationnel manquant ? Partagez vos réflexions avec nous dans les commentaires.

Références