Of je het nu leuk vindt of niet, parallaxing is niet meer weg te denken. Wanneer het verstandig wordt gebruikt, kan het diepte en subtiliteit toevoegen aan een webapp. Het probleem is echter dat het implementeren van parallaxing op een performante manier een uitdaging kan zijn. In dit artikel bespreken we een oplossing die zowel performant is als, net zo belangrijk, cross-browser werkt.

Kortom
- Gebruik geen scroll-gebeurtenissen of
background-position
om parallax-animaties te maken. - Gebruik CSS 3D-transformaties om een nauwkeuriger parallax-effect te creëren.
- Gebruik voor Mobile Safari
position: sticky
om ervoor te zorgen dat het parallax-effect wordt verspreid.
Wil je de kant-en-klare oplossing? Ga dan naar de GitHub-repository UI Element Samples en download de Parallax helper JS ! Je kunt een live demo van de parallax-scroller bekijken in de GitHub-repository.
Probleem-parallaxers
Laten we eerst eens kijken naar twee veelvoorkomende manieren om een parallaxeffect te bereiken. We leggen ook uit waarom deze manieren niet geschikt zijn voor onze doeleinden.
Slecht: gebruik van scroll-gebeurtenissen
De belangrijkste vereiste voor parallaxing is dat het scroll-gekoppeld moet zijn; voor elke verandering in de scrollpositie van de pagina moet de positie van het parallaxerende element worden bijgewerkt. Hoewel dat eenvoudig klinkt, is een belangrijk mechanisme van moderne browsers hun vermogen om asynchroon te werken. Dit geldt in ons specifieke geval voor scrollgebeurtenissen. In de meeste browsers worden scrollgebeurtenissen geleverd als "best-effort" en is er geen garantie dat ze op elk frame van de scrollanimatie worden geleverd!
Deze belangrijke informatie vertelt ons waarom we een JavaScript-gebaseerde oplossing die elementen verplaatst op basis van scrollgebeurtenissen moeten vermijden: JavaScript garandeert niet dat parallaxing gelijk loopt met de scrollpositie van de pagina . In oudere versies van Mobile Safari werden scrollgebeurtenissen daadwerkelijk aan het einde van de scroll afgeleverd, waardoor het onmogelijk was om een JavaScript-gebaseerd scrolleffect te creëren. Recentere versies leveren wel scrollgebeurtenissen tijdens de animatie, maar, net als Chrome, op basis van "best effort". Als de hoofdthread bezig is met andere werkzaamheden, worden scrollgebeurtenissen niet direct afgeleverd, waardoor het parallaxeffect verloren gaat.
Slecht: background-position
bijwerken
Een andere situatie die we willen vermijden, is het tekenen op elk frame. Veel oplossingen proberen background-position
te wijzigen om de parallax-look te creëren, waardoor de browser de betreffende delen van de pagina opnieuw tekent bij het scrollen. Dat kan duur genoeg zijn om de animatie aanzienlijk te verstoren.
Als we de belofte van parallaxbeweging willen waarmaken, willen we iets dat kan worden toegepast als een versnelde eigenschap (wat vandaag de dag betekent dat we ons moeten beperken tot transformaties en dekking) en dat niet afhankelijk is van scrollgebeurtenissen.
CSS in 3D
Zowel Scott Kellum als Keith Clark hebben veel werk verricht op het gebied van het gebruik van CSS 3D om parallax-bewegingen te realiseren. De techniek die zij gebruiken komt in feite op het volgende neer:
- Stel een bevattend element in om te scrollen met
overflow-y: scroll
(en waarschijnlijk ookoverflow-x: hidden
). - Pas op datzelfde element een
perspective
toe en stel deperspective-origin
in optop left
of0 0
. - Pas een translatie in Z toe op de kinderen van dat element en schaal ze weer omhoog om parallaxbeweging te creëren zonder dat hun grootte op het scherm wordt beïnvloed.
De CSS voor deze aanpak ziet er als volgt uit:
.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);
}
Hierbij wordt uitgegaan van een HTML-fragment als dit:
<div class="container">
<div class="parallax-child"></div>
</div>
Schaal aanpassen voor perspectief
Door het onderliggende element terug te duwen, wordt het kleiner in verhouding tot de perspectiefwaarde. Je kunt berekenen hoeveel het moet worden vergroot met deze vergelijking: (perspectief - afstand) / perspectief . Omdat we waarschijnlijk willen dat het parallaxerende element parallaxeert, maar verschijnt op de grootte die we hebben gemaakt, moet het op deze manier worden vergroot in plaats van het te laten zoals het is.
In het geval van de bovenstaande code is het perspectief 1px en de Z-afstand van het parallax-child
-2px . Dit betekent dat het element met 3x moet worden vergroot. U kunt zien dat dit de waarde is die in de code is ingevoerd: scale(3)
.
Voor alle content waarop geen translateZ
waarde is toegepast, kun je de waarde nul gebruiken. Dit betekent dat de schaal (perspectief - 0) / perspectief is, wat resulteert in een waarde van 1, wat betekent dat de schaal noch omhoog noch omlaag is aangepast. Heel handig, eigenlijk.
Hoe deze aanpak werkt
Het is belangrijk om duidelijk te maken waarom dit werkt, aangezien we die kennis zo meteen gaan gebruiken. Scrollen is in feite een transformatie, en daarom kan het versneld worden; het omvat meestal het verschuiven van lagen met de GPU. Bij een typische scroll, waarbij geen enkel perspectief wordt gebruikt, vindt scrollen 1:1 plaats wanneer het scrollende element en zijn onderliggende elementen worden vergeleken. Als je een element 300px
naar beneden scrolt, worden de onderliggende elementen met dezelfde hoeveelheid naar boven getransformeerd: 300px
.
Het toepassen van een perspectiefwaarde op het scrollende element verstoort dit proces echter; het verandert de matrices die ten grondslag liggen aan de scrolltransformatie. Nu kan een scroll van 300 px de onderliggende elementen slechts 150 px verplaatsen, afhankelijk van de gekozen perspective
en translateZ
waarden. Als een element een translateZ
waarde van 0 heeft, wordt het gescrold met een verhouding van 1:1 (zoals voorheen), maar een element dat in Z van de perspectiefoorsprong wordt geduwd, wordt met een andere snelheid gescrold! Resultaat: parallaxbeweging. En, heel belangrijk, dit wordt automatisch afgehandeld als onderdeel van de interne scrollfunctie van de browser, wat betekent dat er niet naar scroll
hoeft te worden geluisterd of background-position
hoeft te worden gewijzigd.
Een vlieg in de zalf: Mobile Safari
Er zijn kanttekeningen bij elk effect, en een belangrijke kanttekening bij transformaties is het behoud van 3D-effecten op onderliggende elementen. Als er elementen in de hiërarchie staan tussen het element met een perspectief en de parallaxerende onderliggende elementen, wordt het 3D-perspectief "afgeplat", wat betekent dat het effect verloren gaat.
<div class="container">
<div class="parallax-container">
<div class="parallax-child"></div>
</div>
</div>
In de bovenstaande HTML is de .parallax-container
nieuw en zal deze de perspective
effectief afvlakken, waardoor het parallaxeffect verloren gaat. De oplossing is in de meeste gevallen vrij eenvoudig: voeg transform-style: preserve-3d
toe aan het element, waardoor alle 3D-effecten (zoals onze perspectiefwaarde) die hoger in de structuur zijn toegepast, worden doorgegeven.
.parallax-container {
transform-style: preserve-3d;
}
In het geval van Mobile Safari liggen de zaken echter iets ingewikkelder. Het toepassen van overflow-y: scroll
op het containerelement werkt technisch gezien wel, maar ten koste van de mogelijkheid om het scrollende element te 'flippen'. De oplossing is om -webkit-overflow-scrolling: touch
toe te voegen, maar dit zal ook het perspective
afvlakken en we krijgen geen parallax.
Vanuit het oogpunt van progressieve verbetering is dit waarschijnlijk geen al te groot probleem. Als we niet in elke situatie parallax kunnen gebruiken, werkt onze app nog steeds, maar het zou fijn zijn om een tijdelijke oplossing te vinden.
position: sticky
ter redding!
Er is inderdaad wat hulp in de vorm van position: sticky
, waarmee elementen tijdens het scrollen aan de bovenkant van de viewport of een bepaald bovenliggend element kunnen blijven plakken. De specificatie is, net als de meeste andere, behoorlijk omvangrijk, maar bevat een handig klein juweeltje:
Dit lijkt op het eerste gezicht misschien niet veel te betekenen, maar een belangrijk punt in die zin is wanneer het verwijst naar hoe de stickyness van een element precies wordt berekend: "de offset wordt berekend met betrekking tot de dichtstbijzijnde voorouder met een scrollbox" . Met andere woorden, de afstand die nodig is om het sticky element te verplaatsen (zodat het aan een ander element of de viewport gekoppeld lijkt te zijn) wordt berekend vóórdat andere transformaties worden toegepast, niet erna . Dit betekent dat, net als in het eerdere scrollvoorbeeld, als de offset werd berekend op 300 px, er een nieuwe mogelijkheid is om perspectieven (of een andere transformatie) te gebruiken om die offsetwaarde van 300 px te manipuleren voordat deze op sticky elementen wordt toegepast.
Door position: -webkit-sticky
toe te passen op het parallaxerende element, kunnen we het afvlakkingseffect van -webkit-overflow-scrolling: touch
effectief "omkeren". Dit zorgt ervoor dat het parallaxerende element verwijst naar de dichtstbijzijnde voorouder met een scrollende box, in dit geval .container
. Vervolgens past .parallax-container
, net als voorheen, een perspective
toe, die de berekende scrolloffset verandert en een parallaxeffect creëert.
<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);
}
Hiermee wordt het parallax-effect voor Mobile Safari hersteld, wat uitstekend nieuws is!
Plakkerige positioneringswaarschuwingen
Er is hier echter een verschil: position: sticky
verandert wel de parallax-mechanica. Sticky positionering probeert het element, nou ja, aan de scrollende container te plakken, terwijl een niet-sticky versie dat niet doet. Dit betekent dat de parallax met sticky uiteindelijk het omgekeerde is van die zonder:
- Bij
position: sticky
: hoe dichter het element bij z=0 is, hoe minder het beweegt. - Zonder
position: sticky
, hoe dichter het element bij z=0 is, hoe meer het beweegt.
Als dat allemaal wat abstract lijkt, bekijk dan deze demo van Robert Flack, die laat zien hoe elementen zich anders gedragen met en zonder vaste positionering. Om het verschil te zien, heb je Chrome Canary (versie 56 op het moment van schrijven) of Safari nodig.

Een demo van Robert Flack die laat zien hoe position: sticky
het parallax scrollen beïnvloedt.
Diverse bugs en oplossingen
Maar zoals met alles zijn er nog steeds hobbels en bultjes die gladgestreken moeten worden:
- Ondersteuning voor sticky-tekst is inconsistent. Ondersteuning wordt nog steeds geïmplementeerd in Chrome, Edge ondersteunt deze functie volledig niet en Firefox heeft problemen met het tekenen wanneer sticky-tekst wordt gecombineerd met perspectieftransformaties . In dergelijke gevallen is het de moeite waard om een beetje code toe te voegen om alleen
position: sticky
(de versie met het voorvoegsel-webkit-
) toe te voegen wanneer dat nodig is, wat alleen voor Mobile Safari is. - Het effect werkt niet "zomaar" in Edge. Edge probeert scrollen op besturingssysteemniveau af te handelen, wat over het algemeen goed is, maar in dit geval detecteert Edge de perspectiefwijzigingen tijdens het scrollen niet. Om dit te verhelpen, kun je een vaste positie-element toevoegen, aangezien dit Edge lijkt over te schakelen naar een niet-besturingssysteem-scrollmethode en ervoor zorgt dat rekening wordt gehouden met perspectiefwijzigingen.
- "De inhoud van de pagina is gewoon enorm geworden!" Veel browsers houden rekening met de schaal bij het bepalen van de grootte van de pagina-inhoud, maar helaas houden Chrome en Safari geen rekening met perspectief . Dus als er bijvoorbeeld een schaal van 3x op een element wordt toegepast, kun je schuifbalken en dergelijke zien, zelfs als het element een schaal van 1x heeft nadat het
perspective
is toegepast. Dit probleem kan worden opgelost door elementen te schalen vanaf de rechteronderhoek (mettransform-origin: bottom right
), wat werkt omdat te grote elementen dan in het "negatieve gebied" (meestal linksboven) van het scrollbare gebied terechtkomen; scrollbare gebieden laten je nooit content in het negatieve gebied zien of ernaartoe scrollen.
Conclusie
Parallaxing is een leuk effect als het goed doordacht wordt gebruikt. Zoals je ziet, is het mogelijk om het te implementeren op een manier die performant, scroll-gekoppeld en browseronafhankelijk is. Omdat het wat rekenwerk en een beetje boilerplate vereist om het gewenste effect te bereiken, hebben we een kleine helperbibliotheek en een voorbeeld gemaakt. Je vindt ze in onze GitHub-repository met voorbeelden van gebruikersinterface-elementen .
Speel het eens, en laat ons weten hoe het gaat.