Parallaxe performant

Paul Lewis
Robert Flack
Robert Flak

Qu'on aime ou qu'on déteste le produit, la parallaxe est faite pour durer. Lorsqu'ils sont utilisés judicieusement, ils peuvent apporter de la profondeur et de la subtilité à une application Web. Cependant, le problème est que l'implémentation du parallaxe de manière efficace peut s'avérer difficile. Cet article présente une solution à la fois performante et, tout aussi importante, compatible avec plusieurs navigateurs.

Illustration au format parallaxe.

Résumé

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

Si vous souhaitez une solution prête à l'emploi, accédez au dépôt GitHub d'exemples d'éléments d'interface utilisateur et récupérez le JavaScript de l'outil d'aide parallaxe. Vous pouvez voir une démonstration en direct du conteneur de défilement parallaxe dans le dépôt GitHub.

Parallaxes de problèmes

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

Utilisation d'événements de défilement non autorisée

La principale condition du parallaxe est qu'il soit couplé au défilement. Pour chaque changement de position de défilement de la page, la position de l'élément en parallaxe doit être mise à jour. Bien que cela semble simple, l'un des principaux mécanismes des navigateurs modernes 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 diffusés dans la mesure du possible. Il n'est pas garanti qu'ils soient diffusés sur chaque image de l'animation de défilement.

Cette information importante nous explique pourquoi nous devons éviter une solution JavaScript qui déplace les éléments en fonction des événements de défilement : JavaScript ne garantit pas que le parallaxe respectera la position de défilement de la page. Dans les anciennes versions de Mobile Safari, les événements de défilement étaient diffusés à la fin du défilement, ce qui empêchait d'utiliser un effet de défilement basé sur JavaScript. Les versions plus récentes affichent des événements de défilement pendant l'animation, mais, dans la mesure du possible, comme dans Chrome, 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.

Erreur: 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'apparence parallaxe, ce qui oblige le navigateur à repeindre les parties concernées de la page lors du défilement, ce qui peut être assez coûteux pour provoquer des à-coups importants de l'animation.

Si nous voulons tenir la promesse du mouvement parallaxe, nous voulons quelque chose qui puisse être appliqué en tant que propriété accélérée (ce qui implique aujourd'hui de respecter les transformations et l'opacité), et qui ne dépend pas des événements de défilement.

CSS en 3D

Scott Kellum et Keith Clark ont chacun effectué d'importants efforts dans le domaine de l'utilisation de CSS 3D pour obtenir un mouvement parallaxe. Voici la technique à leur disposition:

  • Configurez un élément conteneur pour qu'il fasse défiler l'écran 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.
  • Appliquez une translation en Z aux enfants de cet élément, puis redimensionnez-les pour obtenir un mouvement parallaxe sans affecter leur taille à l'écran.

Le CSS 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 qu'un extrait de code HTML se présente comme suit:

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

Ajuster l'échelle pour la perspective

Si vous repoussez l'élément enfant, il diminue de manière proportionnelle à la valeur de la perspective. Vous pouvez calculer l'ampleur de sa mise à l'échelle à l'aide de l'équation suivante: (perspective - distance) / perspective. Étant donné que nous voulons probablement que l'élément de parallaxe soit en parallaxe, mais qu'il apparaisse dans la taille que nous avons créée, il faudrait l'agrandir 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 être ajusté de 3x, comme vous pouvez le voir, comme vous pouvez le voir est la valeur branchée dans le code : scale(3).

Pour tout contenu auquel aucune valeur translateZ n'est appliquée, vous pouvez remplacer la valeur zéro. Cela signifie que l'échelle est (perspective - 0) / perspective, ce qui correspond à une valeur de 1, ce qui signifie qu'elle n'a pas été augmentée ni réduite. C'est plutôt pratique, vraiment.

Fonctionnement de cette approche

Il est important d'expliquer clairement pourquoi cela fonctionne, car nous allons bientôt utiliser ces connaissances. Le défilement est en effet une transformation, c'est pourquoi il peut être accéléré. Cela implique principalement de déplacer des couches avec le GPU. Dans un défilement classique, sans aucune notion de perspective, le défilement s'effectue de manière 1:1 lorsque l'élément à défilement et ses enfants sont comparés. Si vous faites défiler un élément vers le bas de 300px, ses enfants sont transformés selon la même valeur: 300px.

Toutefois, l'application d'une valeur de perspective à l'élément de défilement modifie les matrices qui sous-tendent 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 fera l'objet d'un défilement à 1:1 (comme c'était le cas auparavant), mais un enfant poussé dans Z loin de l'origine de la perspective fera l'objet d'un défilement à une vitesse différente. Résultat net: mouvement parallaxe. Et surtout, cette opération est gérée automatiquement par 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 pommade: Mobile Safari

Chaque effet comporte des mises en garde, dont une importante concernant les transformations : la conservation des effets 3D sur les éléments enfants. S'il existe des éléments dans la hiérarchie entre l'élément avec 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 aplatit la valeur perspective, et nous perdons 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 lui permet de propager tous les effets 3D (comme notre valeur de perspective) appliqués plus haut dans l'arborescence.

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

Dans le cas de Mobile Safari, cependant, les choses sont un peu plus compliquées. Techniquement, l'application de overflow-y: scroll à l'élément de conteneur fonctionne, mais au prix de faire glisser l'élément de défilement. La solution consiste à ajouter -webkit-overflow-scrolling: touch, mais cela aplatit perspective et nous n'obtiendrons pas de parallaxe.

D'un point de vue d'amélioration progressive, ce n'est probablement pas trop un problème. Si nous ne parvenons pas à utiliser la fonction parallaxe dans toutes les situations, notre application continuera de fonctionner, mais il serait intéressant de trouver une solution de contournement.

position: sticky à la rescousse !

Il existe d'ailleurs une aide, sous la forme de position: sticky, qui permet aux éléments de rester en haut de la fenêtre d'affichage ou d'un élément parent donné pendant le défilement. Les spécifications, comme la plupart d'entre elles, sont assez volumineuses, mais elles contiennent un petit joyau utile:

Cela peut sembler peu significatif à première vue, mais l'un des points clés de cette phrase est lorsqu'elle fait référence à la façon exacte de calcul de la cohésion d'un élément: "le décalage est calculé en fonction de l'ancêtre le plus proche avec une case déroulante". En d'autres termes, la distance permettant de déplacer l'élément persistant (pour qu'il apparaisse attaché à 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, comme dans l'exemple précédent du défilement, 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 à des éléments persistants.

En appliquant position: -webkit-sticky à l'élément de parallaxe, nous pouvons "inverser" l'effet d'aplatissement de -webkit-overflow-scrolling: touch. Cela garantit que l'élément de parallaxe fait référence à l'ancêtre le plus proche avec une case de défilement, qui est 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 parallaxe pour Mobile Safari, une excellente nouvelle à tous les niveaux !

Mises en garde concernant le positionnement persistant

Il y a toutefois une différence dans ce cas de figure: position: sticky modifie la mécanique de parallaxe. Le positionnement persistant tente de coller l'élément au conteneur à défilement, ce qui n'est pas le cas d'une version non persistante. Cela signifie que le parallaxe avec "collant" finit par être l'inverse de celui sans:

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

Si tout cela vous semble un peu abstrait, regardez cette démo de Robert Flack, qui montre comment les éléments se comportent différemment avec et sans positionnement persistant. Pour voir la différence, vous devez utiliser Chrome Canary (à la 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 Flak montrant comment position: sticky affecte le défilement parallaxe.

Différents bugs et solutions

Comme pour toute chose, cependant, il y a encore des bosses et des bosses qui doivent être lissées:

  • Le soutien persistant est incohérent. La prise en charge est toujours en cours d'implémentation dans Chrome, Edge n'est pas entièrement pris en charge, et Firefox peut afficher les bugs lorsque le maintien est associé à des transformations de perspective. Dans ce cas, il peut être utile d'ajouter un peu de code pour n'ajouter position: sticky (la version avec le préfixe -webkit-) que lorsque cela est nécessaire, c'est-à-dire pour Mobile Safari uniquement.
  • L'effet "ne fonctionne pas seulement" dans Edge. Edge tente 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 vers une méthode de défilement sans système d'exploitation, et vous 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 Chrome et Safari ne tiennent pas compte de la perspective. Ainsi, si une échelle de x3 est appliquée à un élément, des barres de défilement et autres éléments peuvent s'afficher, même si l'élément est à 1x après l'application de perspective. Vous pouvez contourner ce problème en mettant à l'échelle les éléments à partir de l'angle inférieur droit (avec transform-origin: bottom right), ce qui fonctionne, car les éléments surdimensionnés deviennent la "région négative" (généralement en haut à gauche) de la zone déroulante. Les zones déroulantes ne vous permettent jamais de voir ni de faire défiler le contenu dans la zone négative.

Conclusion

Le parallaxe est un effet amusant lorsqu'il est utilisé de façon réfléchie. Comme vous pouvez le voir, il est possible de l'implémenter d'une manière performante, multinavigateur et couplée au défilement. Étant donné que cela nécessite un peu de préparation mathématique et une petite quantité de code récurrent pour obtenir l'effet souhaité, nous avons regroupé une petite bibliothèque d'aide et un exemple, que vous trouverez dans notre dépôt GitHub d'exemples d'éléments d'interface utilisateur.

Jouez et dites-nous comment vous vous en sortez.