Fermer

septembre 5, 2024

Une combinaison délicate —

Une combinaison délicate —


Un étudiant m’a récemment demandé de l’aider avec un apparemment problème simple. Elle travaillait sur un site Web pour un café doté d’un en-tête collant, et elle souhaitait que la section des héros juste en dessous de cet en-tête couvre le reste de l’espace vertical disponible dans la fenêtre.

Voici une démo visuelle de l’effet souhaité pour plus de clarté.

On dirait que ça devrait être assez facile, non ? J’étais sûr (lire : trop confiant) que le problème ne prendrait que quelques minutes à résoudre, pour finalement découvrir qu’il s’agissait d’un puits beaucoup plus profond que je ne l’avais supposé.

Avant de plonger dans le vif du sujet, jetons un coup d’œil rapide au balisage initial et au CSS pour voir avec quoi nous travaillons :

<body>
  <header class="header">Header Content</header>
  <section class="hero">Hero Content</section>
  <main class="main">Main Content</main>
</body>
.header {
  position: sticky;
  top: 0; /* Offset, otherwise it won't stick! */
}

/* etc. */

Avec ces déclarations, le .header restera en haut de la page. Et pourtant le .hero l’élément en dessous reste intrinsèquement dimensionné. C’est ce que nous voulons changer.

Le fruit à portée de main

La première impulsion que vous pourriez avoir, comme je l’ai fait, est d’enfermer l’en-tête et le héros dans une sorte de conteneur parent et de donner à ce conteneur 100vh pour qu’il s’étende sur toute la fenêtre. Après cela, nous pourrions utiliser Flexbox pour répartir les enfants et faire grandir le héros pour remplir l’espace restant.

<body>
  <div class="container">
    <header class="header">Header Content</header>
    <section class="hero">Hero Content</section>
  </div>
  <main class="main">Main Content</main>
</body>
.container {
  height: 100vh;
  display: flex;
  flex-direction: column;
}

.hero {
  flex-grow: 1;
}

/* etc. */

Cela semble correct à première vue, mais observez ce qui se passe lorsque vous passez devant le héros.

Voir le stylo [Attempt #1: Container + Flexbox [forked]](https://codepen.io/smashingmag/pen/yLdQgQo) par Philippe.

Voir le stylo Tentative n°1 : Conteneur + Flexbox [forked] par Philippe.

L’en-tête collant reste coincé dans son conteneur parent ! Mais.. pourquoi?

Si vous êtes comme moi, ce comportement n’est pas intuitif, du moins au début. Vous avez peut-être entendu ça sticky est une combinaison de relative et fixed positionnementce qui signifie qu’il participe au flux normal du document mais seulement jusqu’à ce qu’il atteigne les bords de son conteneur défilant, auquel cas il devient fixed. Pendant le visionnage sticky comme une combinaison d’autres valeurs peut être un mnémonique utile, elle ne parvient pas à capturer une différence importante entre sticky et fixed éléments :

UN position: fixed L’élément ne se soucie pas du parent dans lequel il est imbriqué ni de l’un de ses ancêtres. Il sortira du flux normal du document et se placera directement en décalage par rapport à la fenêtre, comme s’il était collé à une certaine distance du bord de l’écran.

A l’inverse, un position: sticky L’élément sera poussé le long des bords de la fenêtre (ou du conteneur de défilement le plus proche), mais il n’échappera jamais aux limites de son parent direct. Eh bien, au moins si vous ne comptez pas visuellement transform-le. Donc une meilleure façon d’y penser pourrait être, voler Chris Coyierque « position: sticky est, en un sens, une initiative d’envergure locale position: fixed.» Il s’agit d’une décision de conception intentionnelle, qui autorise des en-têtes collants spécifiques à une section, comme ceux rendus célèbres par les listes alphabétiques dans les interfaces mobiles.

Voir le stylo [Sticky Section Headers [forked]](https://codepen.io/smashingmag/pen/OJeaWrM) par Philippe.

Voir le stylo En-têtes de section collants [forked] par Philippe.

D’accord, cette approche est donc interdite à notre situation difficile. Nous devons trouver une solution qui n’implique pas de conteneur autour de l’en-tête.

Corrigé, mais pas résolu

Peut-être pouvons-nous rendre notre vie un peu plus simple. Au lieu d’un conteneur, et si nous donnions le .header élément un fixé hauteur de, disons, 150px? Il ne reste plus qu’à définir le .hero la hauteur de l’élément comme height: calc(100vh - 150px).

Voir le stylo [Attempt #2: Fixed Height + Calc() [forked]](https://codepen.io/smashingmag/pen/yLdQgGz) par Philippe.

Voir le stylo Tentative n°2 : hauteur fixe + Calc() [forked] par Philippe.

Cette approche en quelque sorte fonctionne, mais les inconvénients sont plus insidieux que notre dernière tentative car ils peuvent ne pas être immédiatement apparents. Vous avez probablement remarqué que l’en-tête est trop haut et nous voudrions faire quelques calculs pour décider d’une meilleure hauteur.

En anticipant un peu,

  • Et si le .headerLes enfants d’aujourd’hui doivent-ils s’envelopper ou se réorganiser selon différentes tailles d’écran ou grandir pour conserver la lisibilité sur mobile ?
  • Que se passe-t-il si JavaScript manipule le contenu ?

Toutes ces choses pourraient subtilement changer la .headerLa taille idéale de , et la recherche des bonnes valeurs de hauteur pour chaque scénario a le potentiel de dégénérer en un cauchemar de maintenance de points d’arrêt et de nombres magiques ingérables – surtout si l’on considère que cela doit être fait non seulement pour le .header mais aussi le .hero élément qui en dépend.

Je dirais que cette solution de contournement se sent faux. Les hauteurs fixes brisent l’une des principales possibilités de la mise en page CSS – la façon dont les éléments grandissent et rétrécissent automatiquement pour s’adapter à leur contenu – et ne pas compter sur cela rend généralement notre vie difficile. Plus fortpas plus simple.

Il nous reste donc…

Une nouvelle approche

Maintenant que nous avons compris les contraintes avec lesquelles nous travaillons, une autre façon de formuler le problème est que nous voulons que le .header et .hero à collectivement portée 100vh sans dimensionner explicitement les éléments ni les envelopper dans un conteneur. Idéalement, nous trouverions quelque chose c’est déjà le cas 100vh et alignez-les sur cela. C’est là que je me suis rendu compte que display: grid peut fournir exactement ce dont nous avons besoin !

Essayons ceci : nous déclarons display: grid sur le body élément et ajoutez un autre élément avant le .header que nous appellerons .above-the-fold-spacer. Ce nouvel élément a une hauteur de 100vh et s’étend sur toute la largeur de la grille. Ensuite, nous dirons à notre espaceur qu’il doit occuper deux lignes de la grille et nous l’ancrerons en haut de la page.

Cet élément doit être entièrement vide car nous ne voulons jamais qu’il soit visible ou enregistré auprès des lecteurs d’écran. Nous l’utilisons simplement comme une béquille pour indiquer au réseau comment se comporter.

<body>
  <!-- This spacer provides the height we want -->
  <div class="above-the-fold-spacer"></div>

  <!-- These two elements will place themselves on top of the spacer -->
  <header class="header">Header Content</header>
  <section class="hero">Hero Content</section>

  <!-- The rest of the page stays unaffected -->
  <main class="main">Main Content</main>
</body>
body {
  display: grid;
}

.above-the-fold-spacer {
  height: 100vh;
  /* Span from the first to the last grid column line */
  /* (Negative numbers count from the end of the grid) */
  grid-column: 1 / -1;
  /* Start at the first grid row line, and take up 2 rows */
  grid-row: 1 / span 2; 
}

/* etc. */

Ce est l’ingrédient magique.

En ajoutant l’espaceur, nous avons créé deux lignes de grille qui ensemble prendre exactement 100vh. Maintenant, tout ce qu’il reste à faire, en substance, c’est de dire au .header et .hero éléments pour s’aligner sur ces lignes existantes. Nous devons leur dire de commencer par la même ligne de colonne de grille que le .above-the-fold-spacer élément afin qu’ils n’essayent pas de s’asseoir à côté. Mais ceci étant fait… ta-da!

Voir le stylo [The Solution: Grid Alignment [forked]](https://codepen.io/smashingmag/pen/YzoRNdo) par Philippe.

Voir le stylo La solution : l’alignement sur la grille [forked] par Philippe.

La raison pour laquelle cela fonctionne est que un conteneur de grille peut avoir plusieurs enfants occupant la même cellule superposés les uns sur les autres. Dans une telle situation, l’élément enfant le plus haut définit la hauteur totale de la ligne de la grille — ou, dans ce cas, la hauteur combinée des deux lignes (100vh).

Pour contrôler comment exactement les deux éléments visibles se partagent l’espace disponible entre eux, nous pouvons utiliser la grid-template-rows propriété. J’ai fait en sorte que la première ligne utilise min-content plutôt que 1fr. Ceci est nécessaire pour que le .header ne prend pas la même quantité d’espace que le .hero mais au lieu de cela, il ne prend que ce dont il a besoin et laisse le héros avoir le reste.

Voici notre solution complète :


body {
  display: grid;
  grid-template-rows: min-content 1fr;
}

.above-the-fold-spacer {
  height: 100vh;
  grid-column: 1 / -1;
  grid-row: 1 / span 2;
}

.header {
  position: sticky;
  top: 0;
  grid-column-start: 1;
  grid-row-start: 1;
}

.hero {
  grid-column-start: 1;
  grid-row-start: 2;
}

Et voilà : un en-tête collant de taille arbitraire au-dessus d’un héros qui grandit pour remplir l’espace visible restant !

Mises en garde et réflexions finales

Il convient de noter que l’ordre HTML des éléments compte ici. Si l’on définit .above-the-fold-spacer après notre .hero section, il se superposera et bloquera l’accès aux éléments en dessous. Nous pouvons contourner ce problème en déclarant soit order: -1, z-index: -1ou visibility: hidden.

Gardez à l’esprit qu’il s’agit d’un exemple simple. Si vous deviez ajouter une barre latérale à gauche de votre page, par exemple, vous devrez ajuster à quelle colonne commencent les éléments. Néanmoins, dans la majorité des cas, l’utilisation d’une approche CSS Grid sera probablement moins gênante que la tâche de Sisyphe consistant à gérer et coordonner manuellement les valeurs de hauteur de plusieurs éléments.

Un autre avantage de cette approche est qu’elle adaptable. Si vous décidez que vous souhaitez qu’un groupe de trois éléments occupe la hauteur de l’écran plutôt que deux, vous devez alors faire en sorte que l’espaceur invisible s’étende sur trois lignes et attribuer les éléments visibles à celui approprié. Même si le contenu de l’élément héros fait que sa hauteur dépasse 100vhla grille s’adapte sans rien casser. Il est même bien pris en charge dans tous les navigateurs modernes.

Plus je réfléchis à cette technique, plus je suis persuadé qu’elle est finalement assez propre. Là encore, savez-vous comment les avocats peuvent se convaincre de leurs propres arguments ? Si vous pensez à une solution encore plus simple que j’ai négligée, n’hésitez pas à nous contacter et à me le faire savoir !

Éditorial fracassant
(gg, ouais)




Source link