Parallaxe performant

Robert Flack
Robert Flack

Qu'on aime ou déteste ça, le parallaxe est là pour durer. Utilisée à bon escient, elle peut ajouter de la profondeur et de la subtilité à une application Web. Le problème, cependant, est qu'il peut s'avérer difficile d'implémenter le parallaxe de manière efficace. Dans cet article, nous allons vous présenter une solution à la fois performante et, surtout, compatible avec plusieurs navigateurs.

Illustration parallaxe.

Résumé

  • N'utilisez pas d'événements de défilement ni de background-position pour créer des animations parallaxes.
  • Utilisez des transformations 3D CSS pour créer un effet de parallaxe plus précis.
  • Pour Mobile Safari, utilisez position: sticky pour vous assurer que l'effet de parallaxe se propage.

Si vous souhaitez utiliser la solution intégrée, accédez au dépôt GitHub d'exemples d'éléments d'interface utilisateur et récupérez le fichier JS de l'outil d'aide parallaxe. Une démonstration en direct du conteneur de défilement parallaxe est disponible dans le dépôt GitHub.

Parallaxes problématiques

Pour commencer, examinons deux méthodes courantes permettant d'obtenir un effet de parallaxe et, plus particulièrement, pourquoi elles ne sont pas adaptées à nos besoins.

Mauvaise installation: utilisation d'événements de défilement

La principale exigence du parallaxe est qu'elle doit être couplée au défilement. Pour chaque modification de la position de défilement de la page, la position de l'élément de parallaxe doit être mise à jour. Bien que cela semble simple, l'un des mécanismes importants des navigateurs récents est leur capacité à fonctionner de manière asynchrone. Dans notre cas particulier, cela s'applique aux événements de défilement. Dans la plupart des navigateurs, les événements de défilement sont fournis dans la mesure du possible et ne sont pas nécessairement diffusés à chaque image de l'animation de défilement.

Cette information importante nous explique pourquoi nous devons éviter une solution basée sur JavaScript qui déplace les éléments en fonction des événements de défilement : JavaScript ne garantit pas que le parallaxe reste en phase avec la position de défilement de la page. Dans les anciennes versions de Safari pour mobile, les événements de défilement étaient en réalité générés à la fin du défilement, ce qui rendait impossible la création d'un effet de défilement basé sur JavaScript. Les versions plus récentes présentent des événements de défilement pendant l'animation, mais, comme pour Chrome, laissent "le mieux possible". Si le thread principal est occupé par d'autres tâches, les événements de défilement ne seront pas diffusés immédiatement, ce qui signifie que l'effet de parallaxe sera perdu.

Mauvaise mise à jour de background-position

Une autre situation que nous aimerions éviter est de peindre sur chaque cadre. De nombreuses solutions tentent de modifier background-position pour fournir l'aspect parallaxe, ce qui oblige le navigateur à repeindre les parties concernées de la page lors du défilement, ce qui peut s'avérer suffisamment coûteux pour entraîner un à-coup important dans l'animation.

Pour tenir les promesses du mouvement de parallaxe, il faut une propriété qui puisse être appliquée en tant que propriété accélérée (ce qui implique aujourd'hui de respecter les transformations et l'opacité) et qui ne repose pas sur des événements de défilement.

CSS en 3D

Scott Kellum et Keith Clark ont travaillé de manière significative dans le domaine de l'utilisation de la 3D CSS pour obtenir un mouvement de parallaxe. Voici la technique qu'ils utilisent efficacement:

  • Configurez un élément conteneur pour le défilement avec overflow-y: scroll (et probablement overflow-x: hidden).
  • À ce même élément, appliquez une valeur perspective et un perspective-origin défini sur top left ou 0 0.
  • Pour les enfants de cet élément, appliquez une traduction en Z et redimensionnez-les pour fournir un mouvement de parallaxe sans modifier leur taille à l'écran.

Le CSS utilisé pour cette approche se présente comme suit:

.container {
  width: 100%;
  height: 100%;
  overflow-x: hidden;
  overflow-y: scroll;
  perspective: 1px;
  perspective-origin: 0 0;
}

.parallax-child {
  transform-origin: 0 0;
  transform: translateZ(-2px) scale(3);
}

Cela suppose un extrait de code HTML comme celui-ci:

<div class="container">
    <div class="parallax-child"></div>
</div>

Ajustement de l'échelle de la perspective...

Si vous repoussez l'élément enfant, il diminuera proportionnellement à la valeur de la perspective. Vous pouvez calculer la valeur de mise à l'échelle nécessaire à l'aide de l'équation suivante: (perspective - distance) / perspective (perspective - distance) / perspective. Étant donné que nous voulons probablement que l'élément parallaxe parallaxe, mais qu'il apparaisse dans la taille que nous avons créée, il faudrait qu'il soit mis à l'échelle de cette manière plutôt que de le laisser tel quel.

Dans le cas du code ci-dessus, la perspective est de 1px et la distance Z de parallax-child est de -2px. Cela signifie que l'élément devra faire l'objet d'un scaling à la hausse de 3x, ce qui correspond à la valeur intégrée au code : scale(3).

Pour tout contenu auquel aucune valeur translateZ n'est appliquée, vous pouvez remplacer une valeur de zéro. Cela signifie que l'échelle est (perspective - 0) / perspective, ce qui renvoie une valeur de 1, ce qui signifie qu'elle n'a été ni augmentée, ni réduite. Très pratique, en fait.

Fonctionnement de cette approche

Il est important d'expliquer pourquoi cela fonctionne, car nous allons bientôt nous en servir. Le défilement est effectivement une transformation, c'est pourquoi il peut être accéléré. Il implique principalement le déplacement des couches avec le GPU. Dans un défilement classique, c'est-à-dire sans aucune notion de perspective, le défilement s'effectue de manière 1:1 lorsque vous comparez l'élément de défilement et ses enfants. Si vous faites défiler un élément de 300px, ses enfants sont transformés de la même quantité: 300px.

Cependant, l'application d'une valeur de perspective à l'élément de défilement perturbe ce processus ; cela modifie les matrices sous-jacentes à la transformation de défilement. Désormais, un défilement de 300 pixels ne peut déplacer les enfants que de 150 pixels, en fonction des valeurs perspective et translateZ que vous avez choisies. Si un élément a une valeur translateZ de 0, il est défilé à 1:1 (comme c'était le cas auparavant), mais un enfant placé dans Z en dehors de l'origine de la perspective le fait défiler à un rythme différent. Résultat net: mouvement parallaxe. Et surtout, cela est géré automatiquement dans le cadre de la machine de défilement interne du navigateur, ce qui signifie qu'il n'est pas nécessaire d'écouter les événements scroll ni de modifier background-position.

Une mouche en pot: Safari sur mobile

Chaque effet fait l'objet de mises en garde, et l'un des aspects importants pour les transformations concerne la conservation des effets 3D pour les éléments enfants. S'il y a des éléments dans la hiérarchie entre l'élément avec une perspective et ses enfants parallaxes, la perspective 3D est "aplatie", ce qui signifie que l'effet est perdu.

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>

Dans le code HTML ci-dessus, .parallax-container est nouveau. Il va aplatir la valeur perspective, ce qui entraîne la perte de l'effet de parallaxe. Dans la plupart des cas, la solution est assez simple: vous ajoutez transform-style: preserve-3d à l'élément, ce qui entraîne la propagation des effets 3D (comme la valeur de la perspective) qui ont été appliqués plus haut dans l'arborescence.

.parallax-container {
  transform-style: preserve-3d;
}

Dans le cas de Safari pour mobile, les choses sont un peu plus compliquées. Techniquement, l'application de overflow-y: scroll à l'élément conteneur fonctionne, mais au prix de la possibilité de faire glisser l'élément de défilement. La solution consiste à ajouter -webkit-overflow-scrolling: touch, mais cela aplatira également perspective et n'entraînera aucun parallaxe.

Du point de vue des améliorations progressives, ce n'est probablement pas trop un problème. Si nous ne pouvons pas parallaxe dans toutes les situations, notre application fonctionnera tout de même, mais il serait intéressant de trouver une solution de contournement.

position: sticky à la rescousse !

position: sticky permet en fait de "rester" en haut de la fenêtre d'affichage ou d'un élément parent donné pendant le défilement. La spécification, comme la plupart d'entre elles, est assez lourde, mais elle contient un petit joyau utile:

Cela ne semble peut-être pas signifier grand-chose à première vue, mais il s'agit d'un point clé lorsqu'il s'agit de savoir comment, exactement, la rétention d'un élément est calculée: "le décalage est calculé en fonction de l'ancêtre le plus proche avec une zone de défilement". En d'autres termes, la distance à laquelle déplacer l'élément persistant (pour qu'il apparaisse associé à un autre élément ou à la fenêtre d'affichage) est calculée avant l'application de toute autre transformation, et non après. Cela signifie que, tout comme dans l'exemple de défilement précédent, si le décalage a été calculé à 300 pixels, il est possible d'utiliser des perspectives (ou toute autre transformation) pour manipuler cette valeur de décalage de 300 pixels avant de l'appliquer aux éléments persistants.

En appliquant position: -webkit-sticky à l'élément de parallaxe, nous pouvons "inverser" efficacement l'effet d'aplatissement de -webkit-overflow-scrolling: touch. Cela garantit que l'élément parallaxe fait référence à l'ancêtre le plus proche avec une zone de défilement, dans ce cas .container. Ensuite, comme précédemment, .parallax-container applique une valeur perspective, qui modifie le décalage de défilement calculé et crée un effet de parallaxe.

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>
.container {
  overflow-y: scroll;
  -webkit-overflow-scrolling: touch;
}

.parallax-container {
  perspective: 1px;
}

.parallax-child {
  position: -webkit-sticky;
  top: 0px;
  transform: translate(-2px) scale(3);
}

Cela restaure l'effet de parallaxe pour Mobile Safari, ce qui est une excellente nouvelle à tous les niveaux.

Mises en garde concernant le positionnement persistant

Il y a cependant une différence dans ce cas: position: sticky modifie effectivement la mécanique parallaxe. Le positionnement persistant tente de coller l'élément dans le conteneur à défilement, contrairement à une version non persistante. Cela signifie que le parallaxe avec les éléments fixes finit par être l'inverse de celui qui n'en est pas:

  • Avec position: sticky, plus l'élément est proche de z=0, moins il se déplace, moins.
  • Sans position: sticky, plus l'élément est proche de z=0, plus il se déplace plus.

Si tout cela vous semble un peu abstrait, regardez cette démonstration de Robert Flack, qui montre comment les éléments se comportent différemment avec et sans positionnement fixe. Pour voir la différence, vous devez utiliser Chrome Canary (version 56 au moment de la rédaction de ce document) ou Safari.

Capture d&#39;écran de la perspective parallaxe

Démonstration de Robert Flack montrant comment position: sticky affecte le défilement parallaxe.

Divers bugs et solutions de contournement

Toutefois, comme pour tout, il reste des bosses et des bosses à atténuer:

  • L'assistance persistante est incohérente. La prise en charge est toujours en cours de mise en œuvre dans Chrome, Edge n'est pas entièrement compatible et Firefox peint les bugs lorsque l'image fixe est combinée à des transformations de perspective. Dans ce cas, il peut être judicieux d'ajouter un peu de code pour n'ajouter position: sticky (la version avec préfixe -webkit-) que lorsque cela est nécessaire, c'est-à-dire pour Mobile Safari uniquement.
  • L'effet ne "fonctionne pas simplement" dans Edge. Edge essaie de gérer le défilement au niveau du système d'exploitation, ce qui est généralement une bonne chose, mais dans ce cas, il l'empêche de détecter les changements de perspective pendant le défilement. Pour résoudre ce problème, vous pouvez ajouter un élément de position fixe, car cela semble basculer Edge sur une méthode de défilement hors OS et s'assurer qu'il tient compte des changements de perspective.
  • "Le contenu de la page est devenu énorme !" De nombreux navigateurs tiennent compte de cette échelle pour déterminer la taille du contenu d'une page, mais, malheureusement, Chrome et Safari ne tiennent pas compte de la perspective. Ainsi, si une échelle de 3 fois est appliquée à un élément, des barres de défilement et autres éléments similaires peuvent s'afficher, même si l'élément est à 1x après l'application de perspective. Il est possible de contourner ce problème en redimensionnant les éléments à partir de l'angle inférieur droit (avec transform-origin: bottom right). Cela entraîne le développement d'éléments surdimensionnés dans la "zone négative" (généralement en haut à gauche) de la zone déroulante. Les régions déroulantes ne vous permettent jamais d'afficher ni de faire défiler vers le contenu situé dans la région négative.

Conclusion

Le parallaxe est un effet amusant lorsqu'il est utilisé de manière réfléchie. Comme vous pouvez le voir, il est possible de l'implémenter de manière efficace, couplée au défilement et internavigateur. Étant donné que cela nécessite quelques manipulations mathématiques et une petite quantité de code récurrent pour obtenir l'effet souhaité, nous avons créé une petite bibliothèque et un exemple d'aide. Vous les trouverez dans notre dépôt GitHub d'exemples d'éléments d'interface utilisateur.

Jouez et dites-nous comment vous vous en sortez.