Alsacreations.com - Apprendre - Archives (janvier 2024)

Les dernières ressources publiées sur Alsacreations.com

Le: 18 01 2024 à 13:37 Auteur: Raphael

La pseudo-classe :has() réalise le fantasme historique de pouvoir enfin "cibler le parent" en CSS… mais elle fait bien plus que ça !

:has() est une pseudo-classe CSS issue des spécifications "Selectors level 4" où elle est décrite comme "sélecteur relationnel".

Le sélecteur :has() cible un élément en relation avec la liste d'arguments qu'il contient au sein de ses parenthèses. Cela permet de cibler un parent ou ancêtre, mais également un frère précédent dans le DOM.

Commençons justement par une énumération d'exemples de ce qu'est capable de réaliser :has()

Quelques exemples simples

Je cible l'élément <a> à condition qu'il contienne un descendant <img> :

a:has(img) {
}

Je cible l'élément <a> à condition qu'il contienne un enfant direct <img> :

a:has(> img) {
}

Je cible n'importe quel élément du DOM à condition qu'il contienne un descendant <img> :

:has(img) {
}

Je cible l'élément <button> à condition qu'il contienne un descendant de classe .icon ou de classe .text :

button:has(.icon, .text) {
}

Je masque l'élément <svg> s'il contient un descendant <symbol> ou <defs> :

svg:has(symbol, defs) {
  display: none;
}

Je cible l'élément de classe .container à condition qu'il contienne un enfant direct unique <img> :

.container:has(> img:only-child) {
}

Je cible l'élément de classe .form-group à condition qu'il contienne un élément <input> à l'état coché :

.form-group:has(input:checked) {
}

Je cible l'élément <form> à condition qu'il contienne un élément à l'état :focus (quasi identique au sélecteur :focus-within) :

form:has(:focus) {
}

Je cible l'élément <h1> à condition que son frère suivant soit un <p> :

h1:has(+ p) {
}

Je cible l'élément <label> à condition que son frère suivant soit un <input> :

label:has(+ input) {
}

Compatibilité

La compatibilité de :has() a été placée au niveau de Baseline 2023, c'est à dire que l'ensemble des navigateurs supportent cette fonctionnalité depuis (décembre) 2023.

support navigateurs du sélecteur :has(). Source: caniuse.com

Il est parfaitement possible de tester si un navigateur supporte :has() à l'aide de la règle @supports() :

@supports (selector(:has(*))) {
  /* ici les styles si :has() est supporté */
}

@supports not (selector(:has(*))) {
  /* ici les styles si :has() n'est pas supporté */
}

Spécificité

Bien qu'appartenant aux pseudo-classes, :has() ne compte pas dans le calcul de la spécificité des sélecteurs CSS, en revanche son contenu est pris en compte :

div:has(img) {
  /* spécificité (0,0,2) */
  /* :has ne compte pas */
  /* on compte les 2 éléments div et img */
}

div:has(img, #hero) {
  /* spécificité (1,0,1) */
  /* :has ne compte pas */
  /* on compte le poids le plus fort dans la liste (ici l'id #hero) */
}

Combinaisons

:has() peut être combiné avec d'autres sélecteurs tels que :not(), :where() ou :is(). Cependant il n'est pas autorisé d'imbriquer :has() au sein d'un autre :has() (en même temps qui voudrait faire ça ?).

Voici quelques exemples :

  • p:has(:not(span)) : p qui contient tout descendant sauf un span
  • p:not(:has(span)) : p qui ne contient pas de descendant span
  • p:where(:has(div)) : p qui contient un descendant span. Fonctionnellement identique à p:has(span) mais avec une spécificité moindre (0,0,1)
  • :where(p:has(div)) : p qui contient un descendant span. Fonctionnellement identique à p:has(span) mais avec une spécificité nulle (0,0,0)

Quelques cas d'usage utiles

:has() est un sélecteur tout à fait révolutionnaire qui ouvre la voie vers de multiples nouvelles possibilités en CSS.

Il est difficile de rassembler tous les cas d'usage utiles que cette pseudo-classe permet aujourd'hui de résoudre. Voici cependant une petite sélection parmi mes préférés…

Appliquer un effet sur la page si une modale est ouverte

Je souhaite cibler body et lui appliquer un effet de flou via backdrop-filter, uniquement s'il contient un élément ayant à la fois la classe .modal et .is-opened :

body:has(.modal.is-opened) {
  backdrop-filter: blur(8px);
}

Démo sur Codepen

Pour ce cas d'usage précis, il peut être pertinent d'envisage d'utiliser <dialog> qui apporte nativement ces fonctionnalités d'obscurcissement ou de flou de page.

Ajouter des gouttières conditionnellement

Je souhaite une gouttière pour espacer les éléments de ma "Card", mais uniquement si celle-ci possède une image :

.card {
  display: grid;
  grid-template-columns: auto 1fr;
}
.card:has(>img) {
  gap: 20px;
}

Encore plus fort : une gouttière doit s'appliquer uniquement si le groupe de cards a exactement deux enfants :

.card-group:has(> :nth-child(2):last-child) {
  gap: 20px;
}

Démo sur Codepen

Décorer le parent d'une case à cocher au clic

Je veux une couleur de fond sur le parent du label et de la checkbox quand celle-ci est cochée :

.form-group:has(input:checked) {
  background: #ddd;
}

Démo sur Codepen

Adapter les styles au nombre d'enfants

Je souhaite adapter les styles et la taille des enfants selon leur nombre au sein de leur parent :

.parent {

  &:has(> :nth-child(10)) { }
  &:has(> :nth-child(20)) { }
  &:has(> :nth-child(30)) { }

}

Démo sur Codepen

Modifier les variables de la page au clic sur une checkbox

À partir d'une case à cocher située n'importe où dans le DOM, je veux inverser les couleurs des variables de la page au moment où cette checkbox est cochée :

<body>
  ...
  n'importe où dans le DOM
  ...
  <div>
    <input type="checkbox" id="check" class="toggle">
    <label for="check">toggle light/dark</label>
  </div>
</body>
body {
  --bg-color: #eee;
  --text-color: #222;
  background-color: var(--bg-color);
  color: var(--text-color);
}
body:has(.toggle:checked) {
  --bg-color: #222;
  --text-color: #eee;
}

Démo sur Codepen

Bénéficier de Container Queries sans connaître le parent

/* Je crée un Container Query sur le parent de .component (quel qu'il soit) */
:has(> .component) {
  container-type: inline-size;
}
/* J'adapte les styles de .component selon la taille du parent */
.component {
  @container (width > 500px) {
    color: hotpink;
  }
}

Démo sur Codepen

Conclusion

La portée et la puissance du sélecteur :has() dépassent de loin toutes les autres façons historiques de cibler les éléments en CSS.

Grâce à :has() il est possible de cibler n'importe quel élément du DOM à partir de n'importe quel autre élément du DOM.

On pourrait se poser des questions légitimes sur les performances d'un sélecteur permettant de parcourir tout le DOM dans tous les sens.

Dans son article consacré à ce sélecteur, Jen Simmons explique que les travaux sur :has() ont conduit à des spécifications dédiées aux performances et que les navigateurs ont été retravaillés pour s'adapter à ces spécifications.

Les résultats sont excellents (traduction d'un extrait de son article) :

Il est enfin possible d'implémenter un tel sélecteur avec des performances fantastiques, même en présence de grands arbres DOM et d'un grand nombre de sélecteurs :has().

Ressources

Publié par Alsacreations.com

Le: 05 01 2024 à 23:18 Auteur: Yordi

Aujourd'hui, il nous est possible de faire énormément de choses avancés en CSS. Cependant quelque chose qui semble plus ou moins simple n'est pas toujours aussi simple que ce que l'on espérait, voir pas du tout. Le projet sur lequel je travaille affiche énormement de table de données (<table>). Pour certaines d'entre elles, j'aimerais pouvoir mettre en surbrillance la ligne et la colonne qui prolongent cellule que je survole…

Un bouton burger sous forme de 3 rectangles

Il est évident qu'il existe de moches solutions avec Javascript (hum hum), mais il est aussi très simple de le faire avec CSS (codepen).

Comment cibler une ligne de la table ?

Très simplement, en utilisant la pseudo-classe hover sur la ligne (<tr> table-row), on peut changer son background-color.

tr:hover {
  background: antiquewhite;
}

Comment cibler une colonne de la table ?

Un peu plus compliqué pour cette partie…

En Javascript, il est plutôt facile de récupérer l'index de la colonne pour ensuite utiliser CSS et une pseudo-classe :nth-child(), dans toutes les lignes, sélectionner la colonne numéro 4 et lui afficher un background différent.

En CSS, on ne peut pas connaître son index (à l'exception de first et last), remonter la structure et réappliquer plus loin. Par contre, en combinant de simple propriétés comme position, overflow et un pseudo-élément ::before, on pourrait créer l'illusion en s'étendant un peu plus loin que la cellule.

td {
  position: relative;

  &:hover::before {
    content: "";
    position: absolute;
    inset: -100vh 0; /* https://developer.mozilla.org/fr/docs/Web/CSS/inset */
    background: antiquewhite;
    z-index: -1; /* On positione le pseudo-élément en arrière plan */
  }
}

Avec inset: -100vh 0 (équivalent à top: -100vh; right: 0; bottom: -100vh; left: 0;), on va prendre la largeur de la cellule et s'étendre en hauteur.

Maintenant, nous avons bien une surbrillance qui se fait sur les deux axes. Il y a cependant notre colonne qui s'étend un peu trop. En ajoutant un simple overflow: hidden sur notre table, on contiendra tout ça.

Pour voir le résultat: codepen

Retrouvez l'intégralité de ce tutoriel en ligne sur Alsacreations.com

Le: 05 01 2024 à 23:18 Auteur: Yordi

Aujourd'hui, il nous est possible de faire énormément de choses avancés en CSS. Cependant quelque chose qui semble ± simple n'est pas toujours aussi simple que ce que l'on espère, voir pas du tout. Le projet sur lequel je travaille affiche énormément de table de données (<table>). Pour certaines d'entre elles, j'aimerais pouvoir mettre en surbrillance la ligne et la colonne de la cellule que je survole…

Un bouton burger sous forme de 3 rectangles

Il est évident qu'il existe de moches solutions avec Javascript (hum hum), mais il est aussi très simple de le faire avec CSS (codepen).

Comment cibler une ligne de la table ?

Très simplement, en utilisant la pseudo-classe hover sur la ligne (<tr> table-row), on peut changer son background-color.

tr:hover {
  background: antiquewhite;
}

Comment cibler une colonne de la table ?

Un peu plus compliqué pour cette partie…

En Javascript, il est plutôt facile de récupérer l'index de la colonne pour ensuite utiliser CSS et une pseudo-classe :nth-child(). Pour toutes les lignes, sélectionner la colonne numéro 4, et lui afficher un background différent.

En CSS, on ne peut pas connaître son index (à l'exception de first et last), remonter la structure et ensuite réappliquer plus loin. Par contre, en combinant de simple propriétés comme position, overflow et un pseudo-élément ::before, on pourrait créer l'illusion en s'étendant verticalement un peu plus loin que la cellule.

td {
  position: relative;

  &:hover::before {
    content: "";
    position: absolute;
    inset: -100vh 0; /* https://developer.mozilla.org/fr/docs/Web/CSS/inset */
    background: antiquewhite;
    z-index: -1; /* On positione le pseudo-élément en arrière plan */
  }
}

Avec inset: -100vh 0 (équivalent à top: -100vh; right: 0; bottom: -100vh; left: 0;), on va prendre la largeur de la cellule et s'étendre en hauteur.

Maintenant, nous avons bien une surbrillance qui se fait sur les deux axes. Il y a cependant notre colonne qui s'étend un peu trop. En ajoutant un simple overflow: hidden sur notre table, on contiendra tout ça.

Voir le résultat

Publié par Alsacreations.com