Que vous l'aimiez ou non, la parallaxe est là pour durer. Utilisé à bon escient, il peut ajouter de la profondeur et de la subtilité à une application Web. Le problème, cependant, est que l'implémentation du parallaxe de manière performante peut s'avérer difficile. Dans cet article, nous allons aborder une solution à la fois performante et, tout aussi important, fonctionnant sur plusieurs navigateurs.

TL;DR
- N'utilisez pas d'événements de défilement ni
background-position
pour créer des animations de parallaxe. - Utilisez les 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é.
Si vous souhaitez utiliser la solution prête à l'emploi, accédez au dépôt GitHub des exemples d'éléments d'interface utilisateur et récupérez le fichier JS d'assistance pour l'effet parallaxe. Vous pouvez consulter une démonstration en direct du défilement parallaxe dans le dépôt GitHub.
Parallaxes problématiques
Pour commencer, examinons deux méthodes courantes pour obtenir un effet de parallaxe et, en particulier, pourquoi elles ne conviennent pas à nos besoins.
Mauvaise pratique : utiliser des événements de défilement
L'exigence clé de la 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 puisse paraître simple, un mécanisme important 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 fournis de manière "optimale". Il n'est pas garanti qu'ils soient fournis pour chaque frame de l'animation de défilement.
Cette information importante nous indique 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 la parallaxe restera synchronisée avec la position de défilement de la page. Dans les anciennes versions de Safari mobile, les événements de défilement étaient en fait transmis à 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 fournissent des événements de défilement pendant l'animation, mais, comme Chrome, sur la base du "meilleur effort". Si 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.
Incorrect : mise à jour de background-position
Nous souhaitons également éviter de peindre sur chaque frame. De nombreuses solutions tentent de modifier background-position
pour donner un effet de parallaxe, ce qui oblige le navigateur à redessiner les parties concernées de la page lors du défilement. Cela peut être suffisamment coûteux pour saccader considérablement l'animation.
Pour tenir la promesse de l'effet parallaxe, nous avons besoin d'un élément qui peut être appliqué en tant que propriété accélérée (ce qui signifie aujourd'hui s'en tenir aux transformations et à l'opacité) et qui ne repose pas sur les événements de défilement.
CSS en 3D
Scott Kellum et Keith Clark ont tous deux réalisé un travail important dans le domaine de l'utilisation de la 3D CSS pour obtenir un effet de parallaxe. La technique qu'ils utilisent est la suivante :
- Configurez un élément conteneur pour qu'il défile avec
overflow-y: scroll
(et probablementoverflow-x: hidden
). - Appliquez à ce même élément une valeur
perspective
et une valeurperspective-origin
définie surtop left
ou0 0
. - Appliquez une translation en Z aux enfants de cet élément, puis remettez-les à l'échelle pour obtenir un effet de parallaxe sans affecter leur taille à l'écran.
Le CSS de 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);
}
qui suppose un extrait de code HTML comme celui-ci :
<div class="container">
<div class="parallax-child"></div>
</div>
Ajuster l'échelle pour la perspective
Si vous repoussez l'élément enfant, il rétrécit proportionnellement à la valeur de perspective. Vous pouvez calculer le facteur d'agrandissement à l'aide de l'équation suivante : (perspective - distance) / perspective. Comme nous voulons très probablement que l'élément de parallaxe soit parallaxe, mais qu'il apparaisse à la taille que nous avons définie, il doit être mis à l'échelle de cette manière, plutôt que d'être laissé tel quel.
Dans le cas du code ci-dessus, la perspective est de 1 px et la distance Z de parallax-child
est de -2 px. Cela signifie que l'élément devra être mis à l'échelle 3x, comme vous pouvez le voir dans la valeur insérée dans le code : scale(3)
.
Pour tout contenu auquel aucune valeur translateZ
n'est appliquée, vous pouvez la remplacer par zéro. Cela signifie que l'échelle est (perspective - 0) / perspective, ce qui donne une valeur de 1. Cela signifie qu'elle n'a pas été mise à l'échelle vers le haut ni vers le bas. C'est plutôt pratique.
Comment fonctionne cette approche ?
Il est important de comprendre pourquoi cela fonctionne, car nous allons utiliser ces connaissances sous peu. Le défilement est en fait une transformation, c'est pourquoi il peut être accéléré. Il s'agit principalement de déplacer des calques avec le GPU. Dans un défilement typique, c'est-à-dire sans notion de perspective, le défilement se produit de manière 1:1 lorsque l'on compare l'élément de défilement et ses enfants.
Si vous faites défiler un élément vers le bas de 300px
, ses enfants sont transformés vers le haut du même montant : 300px
.
Toutefois, l'application d'une valeur de perspective à l'élément de défilement perturbe ce processus, car elle modifie les matrices qui sous-tendent la transformation de défilement.
Désormais, un défilement de 300 px ne déplacera les enfants que de 150 px, en fonction des valeurs perspective
et translateZ
que vous avez choisies. Si un élément a une valeur translateZ
de 0, il sera défilé à 1:1 (comme avant), mais un enfant éloigné de l'origine de la perspective sur l'axe Z sera défilé à une vitesse différente. Résultat net : mouvement de parallaxe. Et, très important, cela est géré automatiquement dans le cadre du mécanisme 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
.
Le point noir : Mobile Safari
Chaque effet comporte des mises en garde. L'une d'elles, importante pour les transformations, concerne la préservation des effets 3D pour les éléments enfants. S'il existe des éléments dans la hiérarchie entre l'élément avec une perspective et ses enfants avec parallaxe, 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 aplatira 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) qui ont été appliqués plus haut dans l'arborescence.
.parallax-container {
transform-style: preserve-3d;
}
Toutefois, dans le cas de Mobile Safari, les choses sont un peu plus compliquées.
L'application de overflow-y: scroll
à l'élément de conteneur fonctionne techniquement, mais au détriment de la possibilité de faire défiler l'élément de défilement. La solution consiste à ajouter -webkit-overflow-scrolling: touch
, mais cela aplatira également le perspective
et nous n'obtiendrons aucun effet de parallaxe.
Du point de vue de l'amélioration progressive, ce n'est probablement pas un problème majeur. Si nous ne pouvons pas utiliser l'effet parallaxe dans toutes les situations, notre application fonctionnera toujours, mais il serait bien de trouver une solution de contournement.
position: sticky
à la rescousse !
En fait, il existe une aide sous la forme de position: sticky
, qui permet aux éléments de "coller" en haut de la fenêtre d'affichage ou d'un élément parent donné lors du défilement. Comme la plupart des spécifications, celle-ci est assez volumineuse, mais elle contient une petite pépite utile :
Cela ne semble pas avoir beaucoup de sens à première vue, mais un point clé de cette phrase est la façon dont l'ancrage d'un élément est calculé : "le décalage est calculé par rapport à l'ancêtre le plus proche avec une zone de défilement". En d'autres termes, la distance à parcourir par l'élément fixe (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, tout comme dans l'exemple de défilement précédent, si le décalage a été calculé à 300 px, vous pouvez utiliser des perspectives (ou toute autre transformation) pour manipuler cette valeur de décalage de 300 px avant qu'elle ne soit appliquée à des éléments fixes.
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 à l'ancêtre le plus proche avec une zone de défilement, qui dans ce cas est .container
. 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 restaure l'effet de parallaxe pour Mobile Safari, ce qui est une excellente nouvelle pour tous !
Mises en garde concernant le positionnement fixe
Il existe toutefois une différence : position: sticky
modifie la mécanique de la parallaxe. Le positionnement fixe tente de coller l'élément au conteneur de défilement, contrairement à une version non fixe. Cela signifie que la parallaxe avec des extrémités fixes finit par être l'inverse de celle sans :
- Avec
position: sticky
, plus l'élément est proche de z=0, moins il se déplace. - Sans
position: sticky
, plus l'élément est proche de z=0, plus il se déplace.
Si tout cela vous semble un peu abstrait, consultez cette démo de Robert Flack, qui montre comment les éléments se comportent différemment avec et sans positionnement fixe. Pour voir la différence, vous avez besoin de Chrome Canary (version 56 au moment de la rédaction) ou de Safari.

Démonstration de Robert Flack montrant comment position: sticky
affecte le défilement parallaxe.
Bugs et solutions diverses
Cependant, comme pour tout, il reste des points à améliorer :
- La prise en charge des éléments fixes est incohérente. La prise en charge est toujours en cours d'implémentation dans Chrome, Edge n'est pas du tout compatible et Firefox présente des bugs d'affichage lorsque la position fixe est associée à des transformations de perspective. Dans ce cas, il est 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 uniquement pour Mobile Safari. - L'effet ne fonctionne pas "tout simplement" dans Edge. Edge tente de gérer le défilement au niveau de l'OS, 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 à position fixe, car cela semble faire passer Edge à une méthode de défilement non-OS et garantit qu'il 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. Ainsi, si une échelle de 3x est appliquée à un élément, vous pouvez voir des barres de défilement et autres, 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 du coin inférieur droit (avectransform-origin: bottom right
). Cela fonctionne, car les éléments surdimensionnés se développeront dans la "région négative" (généralement en haut à gauche) de la zone de défilement. Les régions de défilement ne vous permettent jamais de voir ni de faire défiler le contenu dans la région négative.
Conclusion
La parallaxe est un effet amusant lorsqu'elle est utilisée de manière réfléchie. Comme vous pouvez le voir, il est possible de l'implémenter de manière performante, couplée au défilement et compatible avec plusieurs navigateurs. Comme cela nécessite un peu de gymnastique mathématique et une petite quantité de code passe-partout pour obtenir l'effet souhaité, nous avons créé une petite bibliothèque d'assistance et un exemple que vous trouverez dans notre dépôt GitHub d'exemples d'éléments d'interface utilisateur.
N'hésitez pas à tester cette fonctionnalité et à nous faire part de vos commentaires.