La Cascade

Rechercher

Zoomer les images dans une grille

par Temani Afif, 9 août 2022, cssgrid, css, article original paru le 8 août 2022 dans CSS Tricks

Temani Afif ajoute un effet de survol aux images dans une grille implicite, pas si simple, mais Temani a le don rare de décortiquer les pbs pour les résoudre simplement.


Il est facile de créer une grille d'images, grâce à CSS Grid. Mais il n'est pas toujours évident de faire des choses amusantes avec la grille une fois les images placées.

Par exemple, vous voulez ajouter un effet de survol aux images pour qu'elles grandissent et s'agrandissent au-delà des rangées et des colonnes où elles se trouvent ? Nous pouvons le faire !

voir CSS grid with expandable images de Temani Afif dans CodePen

Cool, non ? Si vous vérifiez le code, vous ne trouverez pas de JavaScript, ni de sélecteurs complexes, ni même de chiffres magiques. Et ce n'est qu'un exemple parmi tant d'autres que nous allons explorer !

Création de la grille

Le code HTML pour créer la grille est aussi simple qu'une liste d'images dans un conteneur. Nous n'avons pas besoin de plus que cela.

<div class="gallery">
  <img />
  <img />
  <img />
  <!-- etc. -->
</div>

Pour le CSS, nous commençons par définir la grille en utilisant ce qui suit :

.gallery {
  --s: 150px; /* contrôle la taille */
  --g: 10px; /* contrôle les gouttières (gap) */

  display: grid;
  gap: var(--g);
  width: calc(
    3 * var(--s) + 2 * var(--g)
  ); /* 3 fois la taille plus 2 fois la gouttière */
  aspect-ratio: 1;
  grid-template-columns: repeat(3, auto);
}

En résumé, nous avons deux variables, l'une qui contrôle la taille des images et l'autre qui définit la taille de l'espace entre les images. L'aspect-ratio permet de garder les choses en proportion.

Vous vous demandez peut-être pourquoi nous ne définissons que trois colonnes et aucune rangée. Non, je n'ai pas oublié les rangées, mais nous n'avons pas besoin de les définir explicitement. CSS Grid est capable de placer automatiquement des éléments sur des rangées et des colonnes implicites, ce qui signifie qu'il nous fournit autant de rangées que nécessaire, quel que soit le nombre d'images que nous lui envoyons. Nous pouvons définir explicitement les rangées si nous le voulons, mais dans ce cas nous devons ajouter grid-auto-flow : column pour nous assurer que le navigateur créera les colonnes nécessaires pour nous.

Voici un exemple pour illustrer les deux cas. La différence est que l'un suit un flux dans le sens des rangées et l'autre dans le sens des colonnes.

voir difference between row/column de Temani Afif dans CodePen

Maintenant que notre grille est en place, il est temps d'ajouter un peu de style à nos images :

.gallery > img {
  width: 0;
  height: 0;
  min-height: 100%;
  min-width: 100%;
  object-fit: cover;
}

L'effet de survol que nous réalisons repose sur ce CSS. Il vous semblera probablement étrange que nous créions des images sans largeur ni hauteur, mais avec une largeur et une hauteur minimales de 100 %. Mais vous verrez qu'il s'agit d'une astuce assez pratique pour ce que nous essayons de réaliser.

Ce que je fais ici, c'est dire au navigateur que les images doivent avoir une largeur et une hauteur nulles, mais qu'elles doivent aussi avoir une hauteur minimale égale à 100 %... mais 100 % de quoi ? Lorsqu'on utilise des pourcentages, la valeur est relative à quelque chose d'autre. Dans notre cas, l'image est placée à l'intérieur d'une cellule de la grille et nous devons connaître sa taille pour savoir à quoi correspond le pourcentage de 100 %.

Le navigateur ignorera d'abord la valeur min-height : 100% pour calculer la taille des cellules de la grille, mais il utilisera la valeur height : 0 dans son calcul. Cela signifie que nos images ne contribueront pas à la taille des cellules de la grille... car elles n'ont techniquement aucune taille physique. Il en résultera trois colonnes et rangées égales basées sur la taille de la grille (que nous avons définie sur la largeur et le ratio d'aspect de la galerie). La hauteur de chaque cellule de la grille n'est rien d'autre que la variable --s que nous avons définie tout à l'heure (idem pour la largeur).

Maintenant que nous avons les dimensions des cellules de notre grille, le navigateur va l'utiliser avec min-height : 100% (et min-width : 100%) ce qui va forcer les images à remplir complètement l'espace de chaque cellule de la grille. Tout cela peut sembler un peu confus, mais l'idée principale est de s'assurer que la grille définit la taille des images plutôt que l'inverse. Je ne veux pas que l'image définisse la taille de la grille et vous comprendrez pourquoi lorsque nous aurons ajouté l'effet de survol.

Création de l'effet au survol

Ce que nous devons faire, c'est augmenter l'échelle des images lorsqu'elles sont survolées. Pour ce faire, nous pouvons ajuster la largeur et la hauteur de l'image au moment du :hover :

.gallery {
  --f: 1.5; /* contrôle le facteur d'échelle */
}

.gallery img:hover {
  width: calc(var(--s) * var(--f));
  height: calc(var(--s) * var(--f));
}

J'ai ajouté une nouvelle variable personnalisée, --f, au mélange comme facteur d'échelle pour contrôler la taille au survol. Remarquez que je multiplie la variable de taille, --s, par celle-ci pour calculer la nouvelle taille de l'image.

Mais tu avais dit que la taille de l'image doit être de 0. Que se passe-t-il ? Je suis perdu...

Ce que j'ai dit reste vrai, mais je fais une exception pour l'image survolée. Je dis au navigateur qu'une seule image aura une taille différente de zéro — elle contribuera donc à la dimension de la grille — tandis que toutes les autres resteront égales à 0.

La partie gauche montre la grille dans son état naturel, sans aucune image survolée, ce que montre la partie droite. Toutes les cellules de la grille sur le côté gauche sont de taille égale puisque toutes les images n'ont pas de dimensions physiques.

Sur le côté droit, la deuxième image de la première rangée est survolée, ce qui lui donne des dimensions qui affectent la taille de la cellule de la grille. Le navigateur agrandit cette cellule de grille spécifique au survol, ce qui contribue à la taille globale. Et comme la taille de l'ensemble de la grille est définie (puisque nous avons défini une largeur fixe pour la .galerie), les autres cellules de la grille vont logiquement répondre en devenant plus petites afin de conserver la taille globale de la .galerie.

C'est notre effet de zoom en action ! En augmentant la taille d'une seule image, nous affectons l'ensemble de la configuration de la grille, et nous avons dit précédemment que la grille définit la taille des images de sorte que chaque image s'étire dans sa cellule de grille pour remplir tout l'espace.

À cela, nous ajoutons une touche de transition et utilisons objet-fit pour éviter la distorsion des images et l'illusion est parfaite !

Je sais que la logique de cette astuce n'est pas facile à comprendre. Ne vous inquiétez pas si vous ne la comprenez pas complètement. Le plus important est de comprendre la structure du code utilisé et comment le modifier pour obtenir plus de variations. C'est ce que nous allons faire maintenant !

Ajout d'images supplémentaires

Nous avons créé une grille 3×3 pour expliquer l'astuce principale, mais vous avez deviné que nous n'avions pas besoin de nous arrêter là. Nous pouvons faire varier le nombre de colonnes et de rangées et ajouter autant d'images que nous le souhaitons.

voir Variable columns/rows de Temani Afif dans CodePen
.gallery {
  --n: 3; /* nombre de lignes*/
  --m: 4; /* nombre de colonnes */
  --s: 150px; /* contrôle de la taille */
  --g: 10px; /* contrôle des gouttières */
  --f: 1.5; /* contrôle le facteur d'échelle */

  display: grid;
  gap: var(--g);
  width: calc(var(--m) * var(--s) + (var(--m) - 1) * var(--g));
  height: calc(var(--n) * var(--s) + (var(--n) - 1) * var(--g));
  grid-template-columns: repeat(var(--m), auto);
}

Nous avons deux nouvelles variables pour le nombre de lignes et de colonnes. Ensuite, nous définissons simplement la largeur et la hauteur de notre grille en les utilisant. Même chose pour grid-template-columns qui utilise la variable --m. Et comme précédemment, nous n'avons pas besoin de définir explicitement les rangées puisque la fonction de placement automatique de la grille CSS fera le travail pour nous, quel que soit le nombre d'éléments d'image que nous utilisons.

Pourquoi pas des valeurs différentes pour la largeur et la hauteur ? Nous pouvons le faire :

voir Variable width/height de Temani Afif dans CodePen
.gallery {
  --n: 3; /* nombre de rangées*/
  --m: 4; /* nombre de colonnes */
  --h: 120px; /* contrôle de la hauteur */
  --w: 150px; /* contrôle la largeur */
  --g: 10px; /* contrôle des gouttières */
  --f: 1.5; /* contrôle le facteur d'échelle */

  display: grid;
  gap: var(--g);
  width: calc(var(--m) * var(--w) + (var(--m) - 1) * var(--g));
  height: calc(var(--n) * var(--h) + (var(--n) - 1) * var(--g));
  grid-template-columns: repeat(var(--m), auto);
}

.gallery img:hover {
  width: calc(var(--w) * var(--f));
  height: calc(var(--h) * var(--f));
}

Nous remplaçons --s par deux variables, une pour la largeur, --w, et une autre pour la hauteur, --h. Puis nous ajustons tout le reste en conséquence.

Nous avons donc commencé par une grille avec une taille et un nombre d'éléments fixes, mais nous avons ensuite créé un nouvel ensemble de variables pour obtenir la configuration que nous voulons. Tout ce que nous avons à faire est d'ajouter autant d'images que nous le souhaitons et d'ajuster les variables CSS en conséquence. Les combinaisons sont illimitées !

Une galerie d'images en plein écran

Et pourquoi pas une version plein écran ? Oui, c'est également possible. Tout ce dont nous avons besoin c'est de savoir quelles valeurs nous devons attribuer à nos variables. Si nous voulons N rangées d'images et que nous voulons que notre grille soit en plein écran, nous devons d'abord résoudre une hauteur de 100vh :

var(--n) * var(--h) + (var(--n) - 1) * var(--g) = 100vh

Même logique pour la largeur, mais en utilisant vw au lieu de vh :

var(--m) * var(--w) + (var(--m) - 1) * var(--g) = 100vw

On fait le calcul pour obtenir :

--w : (100vw - (var(--m) - 1) * var(--g)) / var(--m)
--h : (100vh - (var(--n) - 1) * var(--g)) / var(--n)

C'est fait !

voir Full screen grid de Temani Afif dans CodePen

Il s'agit exactement du même code HTML, mais avec quelques variables mises à jour qui modifient la taille et le comportement de la grille.

Notez que j'ai omis la formule que nous avions précédemment définie pour la largeur et la hauteur du .gallery et que je l'ai remplacée par 100vw et 100vh, respectivement. La formule nous donnera le même résultat mais comme nous savons quelle valeur nous voulons, nous pouvons nous débarrasser de toute cette complexité supplémentaire.

Nous pouvons également simplifier les formules --h et --w en supprimant la gouttière de l'équation en faveur de ceci :

--h: calc(
  100vh / var(--n)
); /* Hauteur de la fenêtre divisée par le nombre de rangées */
--w: calc(
  100vw / var(--m)
); /* Largeur de la fenêtre divisée par le nombre de colonnes */

L'image survolée sera donc un peu plus grande que dans l'exemple précédent, mais ce n'est pas grave puisque nous pouvons contrôler l'échelle avec la variable --f que nous utilisons comme multiplicateur.

Et puisque les variables sont utilisées à un seul endroit, nous pouvons encore simplifier le code en les supprimant complètement :

voir Simplifying the formula #2 de Temani Afif dans CodePen

Il est important de noter que cette optimisation s'applique uniquement à l'exemple en plein écran et non aux exemples que nous avons couverts. Cet exemple est un cas particulier où nous pouvons alléger le code en supprimant une partie du travail de calcul complexe dont nous avions besoin dans les autres exemples.

Nous disposons en fait de tout ce dont nous avons besoin pour créer le modèle populaire de panneaux extensibles :

voir One dimension grid de Temani Afif dans CodePen

Creusons encore plus profond

Avez-vous remarqué que notre facteur d'échelle peut être inférieur à 1 ? Nous pouvons définir la taille de l'image survolée comme étant inférieure à --h ou --w, mais l'image s'agrandit au survol.

La taille initiale de la cellule de la grille est égale à --w et --h, alors pourquoi une valeur plus petite rend-elle la cellule de la grille plus grande ? La cellule ne devrait-elle pas devenir plus petite, ou au moins conserver sa taille initiale ? Et quelle est la taille finale de la cellule de la grille ?

Nous devons approfondir la manière dont l'algorithme CSS Grid calcule la taille des cellules de la grille. Et cela implique de comprendre l'alignement extensible (stretch alignment) par défaut de CSS Grid.

Voici un exemple pour comprendre la logique.

voir Untitled de Temani Afif dans CodePen

Dans la partie gauche de la démo, j'ai défini une double colonne avec une largeur automatique. Nous obtenons le résultat intuitif : deux colonnes égales (et deux cellules de grille égales). Mais la grille que j'ai définie sur le côté droit de la démo, où je mets à jour l'alignement à l'aide de place-content : start, semble ne rien avoir.

DevTools nous aide en montrant ce qui se passe réellement dans les deux cas :

Dans la deuxième grille, nous avons deux colonnes, mais leurs largeurs sont égales à zéro, de sorte que nous obtenons deux cellules de grille qui sont effondrées dans le coin supérieur gauche du conteneur de la grille. Il ne s'agit pas d'un bogue mais du résultat logique de l'alignement de la grille. Lorsque nous dimensionnons une colonne (ou une rangée) avec auto, cela signifie que c'est son contenu qui dicte sa taille — mais nous avons une div vide sans contenu à placer.

Cependant, comme l'alignement par défaut est l'alignement stretch et que nous avons suffisamment d'espace à l'intérieur de notre grille, le navigateur va étirer les deux cellules de la grille de manière égale pour couvrir toute cette zone. C'est ainsi que la grille de gauche se retrouve avec deux colonnes égales.

Extrait de la spécification :

Notez que certaines valeurs de justify-content et align-content peuvent entraîner l'espacement des pistes (space-around, space-between, space-evenly) ou leur redimensionnement (stretch).

Notez le mot "redimensionner" qui est la clé ici. Dans le dernier exemple, j'ai utilisé place-content, qui est l'abréviation de justify-content et align-content.

Et ceci est caché quelque part dans les spécifications de l'algorithme de redimensionnement de la grille :

Cette étape développe les pistes qui ont une fonction de dimensionnement de piste max automatique en divisant tout espace libre positif et défini restant de manière égale entre elles. Si l'espace libre est indéfini, mais que le conteneur de la grille a une largeur/hauteur minimale définie, utilisez cette taille pour calculer l'espace libre pour cette étape.

L'expression "de manière égale" explique pourquoi nous nous retrouvons avec des cellules de grille égales, mais elle s'applique à "l'espace libre", qui est très important.

Reprenons l'exemple précédent et ajoutons du contenu à l'une des div :

voir Untitled de Temani Afif dans CodePen

Nous avons ajouté une image carrée de 50px. Voici une illustration de la façon dont chaque grille de notre exemple réagit à cette image :

Dans le premier cas, nous pouvons voir que la première cellule (en rouge) est plus grande que la seconde (en bleu). Dans le deuxième cas, la taille de la première cellule change pour s'adapter à la taille physique de l'image tandis que la deuxième cellule reste sans dimension. L'espace libre est divisé de manière égale, mais la première cellule a plus de contenu, ce qui la rend plus grande.

Voici le calcul pour déterminer notre espace libre :

(largeur de la grille) - (espace) - (largeur de l'image) = (espace libre)
200px - 5px - 50px = 145px

Divisé par deux — le nombre de colonnes — nous obtenons une largeur de 72,5px pour chaque colonne. Mais nous ajoutons la taille de l'image, 50px, à la première colonne, ce qui nous laisse une colonne de 122,5px et la seconde de 72,5px.

La même logique s'applique à notre grille d'images. Toutes les images ont une taille égale à 0 (aucun contenu), tandis que l'image survolée contribue à la taille — même si ce n'est que de 1px — rendant sa cellule de grille plus grande que les autres. Pour cette raison, le facteur d'échelle peut être n'importe quelle valeur supérieure à 0, même les décimales entre 0 et 1.

Pour obtenir la largeur finale des cellules de la grille, nous effectuons le même calcul pour obtenir ce qui suit :

(largeur du conteneur) - (somme de tous les espaces) - (largeur de l'image survolée) = (espace libre)

La largeur du conteneur est définie par :

var(--m)*var(--w) + (var(--m) - 1)*var(--g)

...et tous les espaces libres sont égaux à :

(var(--m) - 1)*var(--g)

...et pour l'image survolée nous avons :

var(--w)*var(--f)

Nous pouvons calculer tout cela avec nos variables :

var(--m)*var(--w) - var(--w)*var(--f) = var(--w)*(var(--m) - var(--f))

Le nombre de colonnes est défini par --m ,donc nous divisons cet espace libre de manière égale pour obtenir :

var(--w)*(var(--m) - var(--f))/var(--m)

...ce qui nous donne la taille des images non survolées. Pour les images survolées, nous avons ceci :

var(--w)*(var(--m) - var(--f))/var(--m) + var(--w)*var(--f)
var(--w)*((var(--m) - var(--f))/var(--m) + var(--f))

Si nous voulons contrôler la taille finale de l'image survolée, nous utilisons la formule ci-dessus pour obtenir la taille exacte que nous voulons. Si, par exemple, nous voulons que l'image soit deux fois plus grande :

(var(--m) - var(--f))/var(--m) + var(--f) = 2

Ainsi, la valeur de notre multiplicateur d'échelle, --f, doit être égale à :

var(--m)/(var(--m) - 1)

Pour trois colonnes, nous aurons 3/2 = 1,5 et c'est le facteur d'échelle que j'ai utilisé dans la première démo de cet article car je voulais que l'image soit deux fois plus grande au survol !

La même logique s'applique au calcul de la hauteur et dans le cas où nous voulons contrôler les deux indépendamment, nous devrons considérer deux facteurs d'échelle pour nous assurer que nous avons une largeur et une hauteur spécifiques au survol.

.gallery {
  /* même chose que précédemment */
  --fw: 1.5; /* contrôle le facteur d'échelle pour la largeur */
  --fh: 1.2; /* contrôle le facteur d'échelle pour la hauteur */

  /* même chose qu'avant */
}

.gallery img:hover {
  width: calc(var(--w) * var(--fw));
  height: calc(var(--h) * var(--fh));
}

Vous connaissez maintenant tous les secrets pour créer n'importe quel type de grille d'images avec un effet de survol cool, tout en contrôlant la taille que vous souhaitez à l'aide des calculs que nous venons de voir.

Conclusion

Dans mon dernier article, nous avons créé une grille d'aspect complexe avec quelques lignes de CSS qui mettaient à profit les fonctionnalités de grille implicite et de placement automatique de CSS Grid. Dans cet article, nous nous sommes appuyés sur une astuce de dimensionnement de CSS Grid pour créer une grille fantaisiste dans laquelle les images s'agrandissent au survol de l'écran, entraînant l'ajustement de la grille en conséquence. Tout cela avec un code simplifié et facile à ajuster à l'aide de variables CSS !

Dans le prochain article, nous allons jouer avec les formes ! Nous allons combiner la grille CSS avec mask et clip-path pour obtenir une grille d'images originale.

Voir la liste des articles de Temani Afif traduits dans La Cascade.
Article original paru le 8 août 2022 dans CSS Tricks
Traduit avec l'aimable autorisation de CSS Tricks et de Temani Afif.
Copyright CSS Tricks © 2022