L'effet de parallaxe au défilement est une technique visuelle qui consiste à faire défiler différentes couches de contenu à des vitesses variables lorsque l'utilisateur fait défiler une page vers le bas. Cela crée une illusion de profondeur, les éléments au premier plan semblant se déplacer plus rapidement que ceux en arrière-plan, ce qui offre une expérience de navigation attrayante et immersive. Pour obtenir cet effet, il est préférable d'utiliser des animations CSS liées au défilement, qui vous permettent d'associer des animations à la position de défilement d'un conteneur.
Mise en œuvre
Voici comment créer un effet parallaxe de base :
Créer un élément wrapper : cet élément regroupe simplement toutes les couches de l'effet parallaxe. Il ne s'agit pas de l'élément à faire défiler. Son dépassement doit donc être tronqué. Attribuez-lui également un
heightcorrespondant à la hauteur de l'une des couches de l'effet de parallaxe.<div class="wrapper"> … </div>.wrapper { overflow: clip; height: 100vh; /* Height of one of the layers of the parallax */ }Déclarez les couches : dans le wrapper, ajoutez les couches individuelles qui se déplaceront à des vitesses différentes.
<div class="wrapper"> <div class="layer">LAYER 0</div> <div class="layer">LAYER 1</div> <div class="layer">LAYER 2</div> … </div>Ajouter une animation de translation : définissez une animation CSS qui modifie la propriété
transformdes calques. Pour un effet de parallaxe, vous utiliserez généralementtranslateYpour déplacer les calques verticalement.@keyframes parallax { from { transform: translateY(700px); } }Configurer
view-timeline: pour lier l'animation à la position de défilement, créez unview-timelinesur l'élément wrapper, puis appliquez-le aux calques..wrapper { view-timeline: --wrapper; } .layer { animation: parallax linear both; animation-timeline: --wrapper; }Échelonner les animations : pour que les calques se déplacent à des vitesses différentes, vous pouvez utiliser l'une des deux approches principales : échelonner les images clés ou échelonner les
animation-range.Ces deux approches peuvent utiliser des valeurs codées en dur ou l'implémentation
sibling-index()/sibling-count(). Les valeurs codées en dur sont les plus simples et sont également utiles lorsque vous n'avez qu'un nombre limité de couches. L'implémentationsibling-index()/sibling-count()est pratique lorsque vous avez de nombreuses couches.Décalage dans les images clés :
À l'aide de valeurs codées en dur, vous pouvez définir une propriété personnalisée pour chaque calque afin de contrôler manuellement son décalage de parallaxe.
.layer:nth-child(1) { --offset: 100px; } .layer:nth-child(2) { --offset: 200px; } .layer:nth-child(3) { --offset: 300px; } @keyframes parallax { from { transform: translateY(var(--offset)); } }En utilisant
sibling-index(), laissez la fonctionsibling-index()renvoyer l'index d'un élément enfant parmi ses éléments frères pour calculer automatiquement l'effet d'échelonnement.@keyframes parallax { from { transform: translateY(calc(100px * sibling-index())); } }Échelonner les
animation-range:En utilisant des valeurs codées en dur, vous pouvez définir explicitement les limites de
animation-rangesur chaque couche individuellement..layer:nth-child(1) { animation-range: entry 25% exit 50%; } .layer:nth-child(2) { animation-range: entry 25% exit 75%; } .layer:nth-child(3) { animation-range: entry 25% exit 100%; }À l'aide de
sibling-index()etsibling-count(), vous pouvez calculer la plage mathématiquement en fonction du nombre total de couches (sibling-count())..layer { animation-range: entry 25% exit calc(100% / sibling-count() * sibling-index()); }
Exemple de code
@keyframes parallax {
from {
transform: translateY(calc(100px * sibling-index()));
}
}
.wrapper {
view-timeline: --wrapper;
}
.layer {
animation: parallax linear both;
animation-timeline: --wrapper;
}
@media (prefers-reduced-motion: reduce) {
.layer {
animation: none;
}
}
Vous pouvez également utiliser la propriété animation-range pour obtenir un effet similaire :
@keyframes parallax {
from {
transform: translateY(700px);
}
}
.wrapper {
view-timeline: --wrapper;
}
.layer {
animation: parallax linear both;
animation-timeline: --wrapper;
animation-range: entry 25% exit calc(100% / sibling-count() * sibling-index());
}
@media (prefers-reduced-motion: reduce) {
.layer {
animation: none;
}
}
Bonnes pratiques
Lorsque vous utilisez des animations basées sur le défilement, il est important de suivre quelques bonnes pratiques pour garantir une expérience fluide et accessible :
- À FAIRE : incluez la détection de fonctionnalités. Tous les navigateurs ne sont pas compatibles avec les animations déclenchées par le défilement. Utilisez
@supports ((animation-timeline: view()) and (animation-range: entry))pour vérifier la compatibilité et fournir une solution de remplacement pour les navigateurs qui ne sont pas compatibles.- La vérification
(animation-range: entry)DOIT être incluse ici pour filtrer les navigateurs qui ne sont que partiellement compatibles. - N'UTILISEZ PAS le package
scroll-timeline-polyfillpour la stratégie de remplacement, car il n'est pas complet et présente de nombreux problèmes connus. - Si l'animation n'est considérée que comme décorative, optez pour l'amélioration progressive et NE FOURNISSEZ PAS de solution de remplacement.
- La vérification
- À FAIRE : Respectez les préférences des utilisateurs. Certains préfèrent avoir moins de mouvements sur le Web. Utilisez la requête média
prefers-reduced-motionpour désactiver ou réduire vos animations pour ces utilisateurs. - À FAIRE : Essayez de n'animer que les propriétés CSS performantes. Pour des animations plus fluides, n'animez que les propriétés pouvant être gérées par le thread du compositeur du navigateur, telles que
transformetopacity. L'animation d'autres propriétés telles quewidthouheightpeut entraîner des problèmes de performances. - À FAIRE : Utilisez l'ordre de déclaration correct. Lorsque vous utilisez la propriété abrégée
animation, déclarezanimation-timelineetanimation-rangeaprès pour éviter que la propriété abrégée ne réinitialise la chronologie.
Pour définir le animation-range :
- À FAIRE : Donnez à toutes les couches le même décalage de début, par exemple
entry 25%. - À FAIRE : attribuez à chaque calque un décalage de fin différent qui utilise
sibling-count()etsibling-index()pour répartir les décalages, par exempleexit calc(100% / sibling-count() * sibling-index()).
Compatibilité des navigateurs et stratégies de remplacement
Par conséquent, une stratégie de remplacement est généralement requise.
Pour les navigateurs qui ne sont pas compatibles avec les animations déclenchées par le défilement, vous pouvez utiliser une solution de remplacement pour recréer les effets visuels. Les solutions de remplacement sont généralement créées avec un écouteur de défilement (pour les effets ScrollTimeline) ou l'API IntersectionObserver (pour les effets ViewTimeline).
Dans les navigateurs compatibles de manière native avec les animations liées au défilement, utilisez TOUJOURS l'implémentation CSS native, car elle est plus performante.
Notez que tous les effets ne peuvent pas être recréés à l'aide de l'approche de remplacement.
Pour ce cas d'utilisation spécifique, le script suivant applique la solution de remplacement pour les navigateurs qui ne sont pas compatibles avec les animations déclenchées par le défilement. Il utilise un IntersectionObserver pour suivre la visibilité de l'élément .wrapper et met à jour la propriété transform des calques en fonction de la position de défilement.
// Fallback for browsers that don't support scroll-driven animations
if (!CSS.supports('(animation-timeline: view()) and (animation-range: entry)')) {
const wrapper = document.querySelector('.wrapper');
const layers = document.querySelectorAll('.layer');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
window.addEventListener('scroll', onScroll);
} else {
window.removeEventListener('scroll', onScroll);
}
});
}, { threshold: 0 });
observer.observe(wrapper);
function onScroll() {
const scrollY = window.scrollY;
const wrapperRect = wrapper.getBoundingClientRect();
const wrapperTop = wrapperRect.top + scrollY;
const wrapperHeight = wrapperRect.height;
const windowHeight = window.innerHeight;
if (scrollY >= wrapperTop - windowHeight && scrollY <= wrapperTop + wrapperHeight) {
const scrollPercent = (scrollY - (wrapperTop - windowHeight)) / (wrapperHeight + windowHeight);
layers.forEach((layer, index) => {
// This matches the effect as defined in the CSS example above.
// Customize this further if needed.
const initialTranslateY = 100 * index;
const translateY = initialTranslateY * (1 - scrollPercent);
layer.style.transform = `translateY(${translateY}px)`;
});
}
}
// Trigger onScroll once to set initial positions
onScroll();
}