Récemment j'ai dû gérer un cas un peu tricky : une grille d'élément (facile) mais avec 3 niveaux de zoom (et potentiellement plus de niveau à l'avenir). Évidemment l'idée était bien de grossir avec une proportion totale les éléments, le tout avec un design system qui ne propose pas différente taille pour certains composants… 😅

Après discussion avec des collègues, j'ai utilisé transform:scale(x) pour faire les zoom mais c'est pas une propriété aussi magique que ce que je pensais à la base… 😅

Le code de cet article est disponible en ligne et éditable ici : https://stackblitz.com/edit/vitejs-vite-j8fwah

Note : j'utilise du CSS dans cet article, mais on peut faire exactement la même chose avec du CSS avec ou sans pré-processeur. J'utilise ici uniquement le côté imbricable pour plus de lisibilité. Sur le stackblitz, j'utilise des variables SCSS pour améliorer la maintenabilité du code et éviter les magic number.

Utilisation naïve du transform

Je vous propose de travailler avec une grille de carte avec un titre et un bouton. On a pas forcément envie de définir exactement la taille de chaque élément, on sait juste la largeur qu'on veut pour la carte à chaque niveau de zoom (dans mon exemple: small = 150px, medium = 200px et large = 250px) et qu'on veut garder parfaitement les proportions.

Vue de la grille initiale

Le code html correspondant à la grille est :

<div id="container" class="zoom-small">
    <div class="card">
        Element 1
        <button type="button">Click on the 👻</button>
    </div>
    ...
</div>

Naïvement on se dit : c'est simple, il suffit d'appliquer la propriété transform: scale(1.33) pour le second niveau de zoom, on peut gérer le fait d'activer ce changement sur toutes les cartes en mettant une classe sur un élément parent pour indiquer le niveau de zoom, et c'est fini ! Eh bien en fait ça donne ça :

#container_ {
  .card {
    width: 150px;
    height: 100px;
  }

  &.zoom-medium {
    .card {
      transform: scale(1.33);
    }
  }

  &.zoom-large {
    .card {
      transform: scale(1.67);
    }
  }
}

On remarque que les éléments se superposent et on dirait que la grille ne s'adapte pas à la nouvelle taille des cartes… 😱

En fait il faut comprendre plusieurs choses :

  • utiliser un transform: scale(x) ne change l'élément que visuellement, il ne change pas la place qu'il occupe dans le DOM… 😵
  • par défaut les transformations sont effectuées à partir du centre de l'élément…

La solution

Premièrement pour corriger comment la transformation est effectuée, on va dire au navigateur que le centre de la transformation est le coin en haut à gauche. C'est simple, on ajoute ça : transform-origin: left top.

Pour corriger le problème de dimension dans le DOM, j'ai opté pour la solution suivante : je n'ai pas envie de dimensionner chaque élément contenu dans ma carte, mais je connais la taille de carte que je veux à chaque zoom, donc je peux encapsuler mes cartes dans un élément conteneur, et dimensionner ce conteneur, les cartes auront de la place pour se zommer sans que ça pose problème.

Cette solution pose en principal problème qu'on doit déterminer à l'avance toutes les dimensions possibles des cartes. Dans mon cas ce n'est pas du tout un problème comme je sais qu'on a trois niveaux de zoom, et ces niveaux sont prédéfinis.

Niveau html je passe à ça :

<div id="container" class="zoom-small">
    <div class="card">
        <div class="content">
            Element 1
            <button type="button">Click on the 👻</button>
        </div>
    </div>
    ...
</div>

De cette manière, je vais dimensionner explicitement la taille de l'élément .card, et zoomer sur l'élément .content. Le code CSS change aussi, car on est obligé de dimensionner pas mal d'éléments :

#container {
  .card {
    width: 150px;
    height: 100px;
    .content {
      width: 150px;
      height: 100px;
    }
  }

  &.zoom-medium {
    .card {
      width: 200px;
      height: 133px;
      .content {
        transform: scale(1.33);
        transform-origin: left top;
      }
    }
  }

  &.zoom-large {
    .card {
      width: 250px;
      height: 167px;
      .content {
        transform: scale(1.67);
        transform-origin: left top;
      }
    }
  }
}

Conclusion

Alors que ça parait assez simple de prime abord de gérer du zoom comme je le fais là, il faut quand même réfléchir à ce qu'on fait. J'imagine qu'on pourrait faire autrement, mais c'est la solution qui m'a semblé le plus maintenable dans le temps pour mon cas. J'ai un peu raccourci l'histoire, car il m'a fallu quelque chose comme 2h à tester différentes des solutions et lire de la doc pour comprendre ce qu'il se passait et arriver à cette solution.

Pour le cas où je devrais faire un zoom dynamique avec un slider par exemple (avec du coup une plage de zoom et non une liste définie de niveau), je pense qu'on pourrait arriver à faire la même chose mais en utilisant du JavaScript pour définir la taille de .card en fonction de la taille d'origine et ajouter via du JavaScript toujours la valeur du transform: scale(x).

À noter qu'un collègue m'avait aussi proposé la propriété zoom mais celle-ci n'est pas standard donc c'est un grand non pour moi : il ne faut pas utiliser de propriété non standard à moins de vouloir avoir un site qui ne fonctionne sur aucun navigateur, car même si c'est une propriété qui fonctionne à l'instant T sur Chrome, dites-vous qu'elle évoluera sans doute dans une direction qui ne conviendra plus à votre cas !

En tout cas j'ai trouvé la création de ce système de zoom un peu tricky, et je pense que ça valait la peine de le partager ici ! 🤓

Sources :

Crédit photo :