Een parallax-effect bij scrollen is een visuele techniek waarbij verschillende contentlagen met verschillende snelheden bewegen terwijl de gebruiker omlaag scrollt op een pagina. Dit wekt de illusie van diepte, waarbij elementen op de voorgrond sneller lijken te bewegen dan de elementen op de achtergrond. Dit leidt tot een aantrekkelijke en meeslepende browsefunctionaliteit. Dit effect bereik je het beste met CSS Scroll-Driven Animations, waarmee je animaties kunt koppelen aan de scrollpositie van een container.
Hoe wordt dit geïmplementeerd
Zo maakt u een basisparallaxeffect:
Maak een wrapper-element: Dit element groepeert alle lagen van het parallax-effect. Het is niet het scrollbare element, dus de overloop moet worden afgekapt. Geef het ook een
heightdie overeenkomt met de hoogte van een van de lagen van het parallaxeffect.<div class="wrapper"> … </div>.wrapper { overflow: clip; height: 100vh; /* Height of one of the layers of the parallax */ }De lagen definiëren: Voeg in de wrapper de afzonderlijke lagen toe die met verschillende snelheden bewegen.
<div class="wrapper"> <div class="layer">LAYER 0</div> <div class="layer">LAYER 1</div> <div class="layer">LAYER 2</div> … </div>Voeg een vertaalanimatie toe: Definieer een CSS-animatie die de eigenschap
transformvan de lagen wijzigt. Voor een parallax-effect gebruikt u meestaltranslateYom de lagen verticaal te verplaatsen.@keyframes parallax { from { transform: translateY(700px); } }De
view-timelineinstellen: Als u de animatie aan de scrollpositie wilt koppelen, maakt u eenview-timelinevoor het wrapper-element en past u deze toe op de lagen..wrapper { view-timeline: --wrapper; } .layer { animation: parallax linear both; animation-timeline: --wrapper; }De animaties spreiden: Als je de lagen met verschillende snelheden wilt laten bewegen, kun je een van de volgende 2 hoofdbenaderingen gebruiken: spreiden in de keyframes of de
animation-rangespreiden.Beide benaderingen kunnen hardgecodeerde waarden gebruiken of de
sibling-index()/sibling-count()-implementatie. De vastgelegde waarden zijn het makkelijkst en ook nuttig als je maar een beperkt aantal lagen hebt. De implementatie vansibling-index()/sibling-count()is handig als je veel lagen hebt.Spreiding in de sleutelframes:
Met hardcoded waarden kun je een aangepaste eigenschap voor elke laag definiëren om de parallax-offset handmatig te beheren.
.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)); } }Gebruik
sibling-index()en laat de functiesibling-index()de index van een onderliggend element tussen de andere onderliggende elementen retourneren om het verspreide effect automatisch te berekenen.@keyframes parallax { from { transform: translateY(calc(100px * sibling-index())); } }De
animation-rangespreiden:Met hardcoded waarden kunt u de grenzen van de
animation-rangevoor elke laag afzonderlijk definiëren..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%; }Met
sibling-index()ensibling-count()kun je het bereik wiskundig berekenen op basis van het totale aantal lagen (sibling-count())..layer { animation-range: entry 25% exit calc(100% / sibling-count() * sibling-index()); }
Voorbeeldcode
@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;
}
}
Je kunt ook de eigenschap animation-range gebruiken om een vergelijkbaar effect te bereiken:
@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;
}
}
Praktische tips
Als je scrollgestuurde animaties gebruikt, is het belangrijk dat je een aantal best practices volgt om een soepele en toegankelijke gebruikerservaring te bieden:
- WEL functiedetectie opnemen: Niet alle browsers ondersteunen scrollgestuurde animaties. Gebruik
@supports ((animation-timeline: view()) and (animation-range: entry))om te controleren op ondersteuning en een fallback te bieden voor browsers die dit niet ondersteunen.- De
(animation-range: entry)-controle MOET hier worden opgenomen om browsers met slechts gedeeltelijke ondersteuning weg te filteren. - Gebruik het
scroll-timeline-polyfill-pakket NIET voor de fallback-strategie, omdat het niet alle functies bevat en veel bekende problemen heeft. - Als de animatie alleen als decoratief wordt beschouwd, kies je voor progressive enhancement en bied je GEEN fallback.
- De
- WEL: Respecteer gebruikersvoorkeuren. Sommige gebruikers geven de voorkeur aan minder beweging op het web. Gebruik de mediaquery
prefers-reduced-motionom je animaties uit te zetten of te verminderen voor deze gebruikers. - WEL proberen alleen animaties te maken voor presterende CSS-property's: Voor de soepelste animaties maak je alleen animaties voor property's die kunnen worden verwerkt door de compositor-thread van de browser, zoals
transformenopacity. Als je andere eigenschappen animeert, zoalswidthofheight, kan dit leiden tot prestatieproblemen. - WEL de juiste declaratievolgorde gebruiken: Als je de verkorte eigenschap
animationgebruikt, declareer jeanimation-timelineenanimation-rangeerna om te voorkomen dat de verkorte eigenschap de tijdlijn reset.
Zo stel je de animation-range in:
- WEL: Geef alle lagen dezelfde startoffset, bijvoorbeeld
entry 25% - WEL: Geef alle lagen een andere eindoffset die
sibling-count()ensibling-index()gebruikt om de offsets te verdelen, bijvoorbeeldexit calc(100% / sibling-count() * sibling-index()).
Browserondersteuning en fallback-strategieën
Daarom is meestal een reservestrategie vereist.
Voor browsers die geen scrollgestuurde animaties ondersteunen, kun je een fallback gebruiken om de visuele effecten opnieuw te maken. De fallbacks worden meestal gemaakt met een scroll-listener (voor ScrollTimeline-effecten) of de IntersectionObserver API (voor ViewTimeline-effecten).
In browsers met ingebouwde ondersteuning voor scrollgestuurde animaties gebruik je ALTIJD de native CSS-implementatie, omdat die beter presteert.
Niet elk effect kan opnieuw worden gemaakt met de reserveaanpak.
Specifiek voor deze use case past het volgende script de fallback toe voor browsers die geen scrollgestuurde animaties ondersteunen. De functie gebruikt een IntersectionObserver om de zichtbaarheid van het element .wrapper bij te houden en updatet de property transform van de lagen op basis van de scrollpositie.
// 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();
}