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 que l'implémentation un parallaxe performant peut s'avérer difficile. Dans cet article, nous aborderons discuter d'une solution à la fois performante et, tout aussi importante, qui fonctionne multi-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 est propagée.

Si vous souhaitez utiliser la 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 Outil d'aide parallaxe JS ! Vous pouvez regarder une démonstration en direct du conteneur de défilement parallaxe dans la dépôt GitHub.

Parallaxes problématiques

Pour commencer, examinons deux façons courantes d'obtenir un parallaxe et en particulier les raisons pour lesquelles ils ne sont pas adaptés à nos besoins.

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

L'exigence clé du parallaxe est qu'il doit être couplé au défilement. pour à chaque changement de position de défilement de la page, l'attribut de l'élément parallaxe doit être mis à jour. Bien que cela semble simple, un mécanisme important de les navigateurs récents est leur capacité à fonctionner de manière asynchrone. Ceci s'applique dans notre pour faire défiler des événements. Dans la plupart des navigateurs, les événements de défilement sont générés comme "du mieux possible" et leur diffusion n'est pas garantie à chaque image Animation de défilement

Cette information importante nous explique pourquoi nous devons éviter Solution basée sur JavaScript qui déplace les éléments en fonction d'événements de défilement: JavaScript ne garantit pas que le parallaxe restera en phase avec le la position de défilement de la page. Dans les anciennes versions de Safari pour mobile, les événements de défilement livré à la fin du défilement, ce qui rendait impossible Effet de défilement basé sur JavaScript Les versions plus récentes présentent des événements de défilement pendant l'animation. Toutefois, comme pour Chrome, à la base. Si le Le thread principal est occupé par d'autres tâches, les événements de défilement ne seront pas transmis 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. Nombreuses solutions tenter de modifier background-position pour obtenir l'apparence parallaxe, ce qui force le navigateur à repeindre les parties affectées de la page lors du défilement, et que peut s'avérer assez coûteux pour entraîner des à-coups dans l'animation.

Pour tenir la promesse du mouvement parallaxe, nous voulons atteindre peuvent être appliquées en tant que propriété accélérée (ce qui implique aujourd'hui de s'en tenir et l'opacité), et qui ne repose pas sur des événements de défilement.

CSS en 3D

Scott Kellum et Keith Clark ont tous deux a beaucoup travaillé sur l'utilisation de la 3D CSS pour obtenir un mouvement de parallaxe. et la technique qu'ils utilisent est effectivement le suivant:

  • Configurez un élément conteneur pour le faire défiler avec overflow-y: scroll (et probablement overflow-x: hidden).
  • À ce même élément, appliquez une valeur perspective et une propriété perspective-origin définie 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 parallaxe sans affecter 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 réduira sa taille proportionnelle à la du point de vue de l'utilisateur. Vous pouvez calculer la quantité de données à augmenter cette équation: (perspective - distance) / perspective. Puisque nous avons probablement que l'élément de parallaxe soit en parallaxe, mais apparaisse à la taille dans laquelle nous l'avons créé, il faudrait l'augmenter de cette manière, plutôt que de la laisser telle quelle.

Dans le cas du code ci-dessus, la perspective est de 1px, et la macro La distance Z de parallax-child est de -2 px. Cela signifie que l'élément aura besoin à la hausse de 3x, que vous pouvez voir est la valeur intégrée au code: scale(3)

Pour tout contenu auquel aucune valeur translateZ n'est appliquée, vous pouvez : remplacez-la par une valeur de zéro. Cela signifie que l'échelle est (perspective - 0) / la perspective, qui renvoie une valeur de 1, ce qui signifie qu'elle est mise à l'échelle ni vers le haut ni vers le bas. Très pratique, en fait.

Fonctionnement de cette approche

Il est important de bien comprendre pourquoi cela fonctionne, car nous allons l'utiliser connaissances. Le défilement est effectivement une transformation, c'est pourquoi il peut être accelerated; cela implique principalement le déplacement des couches avec le GPU. Dans un c'est-à-dire sans aucune notion de perspective, se produit de manière 1:1 lors de la comparaison de l'élément défilant et de ses enfants. Si vous faites défiler un élément de 300px vers le bas, ses enfants sont transformés vers le haut du même montant: 300px.

Cependant, l'application d'une valeur de perspective à l'élément de défilement perturbe dans 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, selon Valeurs perspective et translateZ que vous avez choisies. Si un élément possède une translateZ définie sur 0, le défilement est effectué à 1:1 (comme c'était le cas auparavant), mais un élément enfant déplacé en Z en dehors de l'origine de la perspective sera défilé à une position différente votre note ! Résultat net: mouvement parallaxe. Et, très important, cela est traité comme une partie de la machine de défilement interne du navigateur, ce qui signifie pas besoin 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 il est important de noter que la préservation des effets 3D sur les éléments enfants. S'il y a des éléments dans 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, le .parallax-container est nouveau et aplatir la valeur perspective et perdre l'effet de parallaxe. La solution, dans la plupart des cas, est assez simple: vous ajoutez transform-style: preserve-3d à l'élément, ce qui entraîne la propagation d'effets 3D (comme notre perspective ) appliquées 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, appliquer overflow-y: scroll à l'élément conteneur fonctionne, mais au le coût de la possibilité de faire glisser l'élément de défilement. La solution consiste à ajouter -webkit-overflow-scrolling: touch, mais cela va aussi aplatir perspective et nous n'aurons pas de parallaxe.

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

position: sticky à la rescousse !

Il existe en fait de l'aide sous la forme de position: sticky, qui permet autoriser les éléments à "s'en tenir" 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 lourdes, mais elles contiennent petit bijou pratique dans:

Cela ne semble peut-être pas grand-chose à première vue, mais un point clé dans cette phrase lorsqu'elle fait référence à la façon dont l'adhésion d'un élément compute: "le décalage est calculé par rapport à l'ancêtre le plus proche à l'aide d'une zone de défilement. En d'autres termes, la distance pour déplacer l'élément collant (pour qu'elle apparaisse associée à un autre élément ou à la fenêtre d'affichage) est Elle est calculée avant l'application de toute autre transformation, et non après. Cela signifie comme dans l'exemple de défilement précédent, si le décalage a été calculé à 300 px, vous pouvez profiter d'une nouvelle opportunité d'utiliser des perspectives (ou toute autre transformation) pour manipuler cette valeur de décalage de 300 pixels avant de l'appliquer à des blocs éléments.

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 de parallaxe fait référence à la avec une zone de défilement (.container dans le cas présent). Ensuite, comme précédemment, .parallax-container applique une valeur perspective, ce 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 permet de rétablir l'effet de parallaxe pour Safari sur mobile, ce qui est une excellente nouvelle. ronde !

Mises en garde concernant le positionnement persistant

Il y a cependant une différence dans le cas présent: position: sticky modifie le la mécanique parallaxe. Le positionnement persistant permet de faire adhérer l'élément au conteneur à défilement, contrairement à une version non persistante. Cela signifie que parallaxe avec fixation finira par être l'inverse de celui sans:

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

Si tout cela vous semble un peu abstrait, regardez cette démonstration de Robert Flack, montre comment les éléments se comportent différemment avec et sans de votre application. Pour voir la différence, vous avez besoin de Chrome Canary (version 56). au moment de la rédaction de cet article) ou Safari.

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

Une démonstration de Robert Flack, position: sticky affecte le défilement parallaxe.

Divers bugs et solutions de contournement

Mais comme pour tout, il reste des bosses et des bosses lissées:

  • L'assistance persistante est incohérente. La prise en charge est toujours en cours Chrome, Edge n'est pas du tout compatible et Firefox peint des bugs lorsque l'image fixe est combinée à des transformations de perspective. Dans ces il peut être utile d'ajouter du code pour n'ajouter que position: sticky (le version avec préfixe -webkit-) lorsque cela est nécessaire, c'est-à-dire pour Safari sur mobile. 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, cela l'empêche de détecter les changements de perspective lors du 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 OS, et s'assure qu'elle tient compte des changements de perspective.
  • "Le contenu de la page est devenu énorme !" De nombreux navigateurs tiennent compte de l'échelle pour déterminer la taille du contenu de la page, mais malheureusement, Chrome et Safari ne tiennent pas compte de la perspective. Donc si une échelle de 3 x est appliquée à un élément, vous pouvez les barres de défilement et d'autres éléments similaires, même si l'élément est 1x après le début "perspective" a été appliqué. Il est possible de contourner ce problème en de mise à l'échelle des éléments à partir de l'angle inférieur droit (avec transform-origin: bottom right), ce qui permet aux éléments surdimensionnés de se développer "région à exclure" (généralement en haut à gauche) de la zone déroulante ; à faire défiler ne vous permettent jamais de voir ni de faire défiler la page vers le contenu correspondant à la zone négative.

Conclusion

Le parallaxe est un effet amusant lorsqu'il est utilisé de manière réfléchie. Comme vous le voyez, il est possible pour l'implémenter de manière performante, couplée au défilement et multinavigateur. Puisqu'il faut un peu de gribouillissement mathématique et une petite quantité de code récurrent pour obtenir l'effet souhaité, nous avons créé une petite bibliothèque d'aide et exemple, que vous pouvez trouver dans notre dépôt GitHub d'exemples d'éléments d'interface utilisateur.

Jouez et dites-nous comment vous vous en sortez.