Houd ervan of haat het, parallaxen is een blijvertje. Bij oordeelkundig gebruik 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.
TL; DR
- Gebruik geen scrollgebeurtenissen of
background-position
om parallaxanimaties te maken. - Gebruik CSS 3D-transformaties om een nauwkeuriger parallaxeffect te creëren.
- Gebruik voor Mobile Safari
position: sticky
om ervoor te zorgen dat het parallax-effect zich verspreidt.
Als je de drop-in-oplossing wilt, ga dan naar de UI Element Samples GitHub-repository en pak de Parallax-helper JS ! Je kunt een live demo van de parallax-scroller zien in de GitHub-repository.
Probleem parallaxers
Laten we om te beginnen eens kijken naar twee veel voorkomende manieren om een parallaxeffect te bereiken, en in het bijzonder waarom ze niet geschikt zijn voor onze doeleinden.
Slecht: scrollgebeurtenissen gebruiken
De belangrijkste vereiste van parallaxen is dat het scroll-gekoppeld moet zijn; voor elke afzonderlijke verandering in de schuifpositie van de pagina moet de positie van het parallaxelement worden bijgewerkt. Hoewel dat eenvoudig klinkt, is een belangrijk mechanisme van moderne browsers hun vermogen om asynchroon te werken. Dit is in ons specifieke geval van toepassing op scroll-gebeurtenissen. In de meeste browsers worden scroll-gebeurtenissen geleverd als "best-effort" en het is niet gegarandeerd dat ze op elk frame van de scroll-animatie worden weergegeven!
Dit belangrijke stukje informatie vertelt ons waarom we een op JavaScript gebaseerde oplossing moeten vermijden die elementen verplaatst op basis van scrollgebeurtenissen: JavaScript garandeert niet dat parallaxing gelijke tred houdt met de scrollpositie van de pagina . In oudere versies van Mobile Safari werden scrollgebeurtenissen feitelijk aan het einde van de scroll weergegeven, waardoor het onmogelijk was om een op JavaScript gebaseerd scrolleffect te maken. Recentere versies leveren scrollgebeurtenissen tijdens de animatie, maar, net als bij Chrome, op een "best effort"-basis. Als de rode draad bezig is met ander werk, worden scrollgebeurtenissen niet onmiddellijk afgeleverd, wat betekent dat het parallaxeffect verloren gaat.
Slecht: background-position
bijwerken
Een andere situatie die we graag willen vermijden is het schilderen op elk frame. Veel oplossingen proberen de background-position
te veranderen om een parallax-look te creëren, waardoor de browser de getroffen delen van de pagina opnieuw tekent bij het scrollen, en dat kan kostbaar 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 tegenwoordig betekent dat we vasthouden aan 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 parallaxbeweging te bereiken, en de techniek die zij gebruiken is in feite deze:
- Stel een bevattend element in om te scrollen met
overflow-y: scroll
(en waarschijnlijkoverflow-x: hidden
). - Pas op datzelfde element een
perspective
toe, en eenperspective-origin
ingesteld optop left
, of0 0
. - Pas op de kinderen van dat element een vertaling in Z toe en schaal ze weer omhoog om parallaxbeweging te bieden zonder hun grootte op het scherm te beïnvloeden.
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 zoals dit:
<div class="container">
<div class="parallax-child"></div>
</div>
Schaal aanpassen voor perspectief
Als u het onderliggende element terugduwt, wordt het kleiner in verhouding tot de perspectiefwaarde. Je kunt berekenen hoeveel het moet worden opgeschaald met deze vergelijking: (perspectief - afstand) / perspectief . Omdat we hoogstwaarschijnlijk willen dat het parallaxende element parallaxt, maar verschijnt op de grootte waarin we het hebben geschreven, zou het op deze manier moeten worden opgeschaald, in plaats van te blijven zoals het is.
In het geval van de bovenstaande code is het perspectief 1px en is de Z-afstand van het parallax-child
-2px . Dit betekent dat het element moet worden opgeschaald met 3x , wat je kunt zien aan de waarde die in de code is ingeplugd: scale(3)
.
Voor alle inhoud waarop geen translateZ
waarde is toegepast, kunt u de waarde nul vervangen. Dit betekent dat de schaal (perspectief - 0) / perspectief is, wat uitkomt op een waarde van 1, wat betekent dat de schaal niet omhoog of omlaag is geschaald. Heel handig, eigenlijk.
Hoe deze aanpak werkt
Het is belangrijk om duidelijk te maken waarom dit werkt, omdat we die kennis binnenkort gaan gebruiken. Scrollen is in feite een transformatie en daarom kan het worden versneld; het gaat meestal om het verschuiven van lagen met de GPU. In een typische scroll, die er één is zonder enig perspectief, gebeurt het scrollen op een 1:1 manier wanneer het scrollelement en zijn kinderen worden vergeleken. Als u een element 300px
naar beneden scrolt, worden de onderliggende elementen met dezelfde hoeveelheid naar boven getransformeerd: 300px
.
Het toepassen van een perspectiefwaarde op het scrollelement zorgt echter voor problemen met dit proces; het verandert de matrices die ten grondslag liggen aan de scrolltransformatie. Nu kan een scroll van 300px de kinderen slechts 150px verplaatsen, afhankelijk van het perspective
en translateZ
waarden die u hebt gekozen. Als een element een translateZ
waarde van 0 heeft, wordt het met een verhouding van 1:1 gescrolld (zoals vroeger), maar een kind dat in Z is geduwd, weg van de perspectiefoorsprong, zal met een andere snelheid worden gescrolld! Nettoresultaat: parallaxbeweging. En, heel belangrijk, dit wordt automatisch afgehandeld als onderdeel van de interne scroll-machinerie van de browser, wat betekent dat het niet nodig is om naar scroll
-gebeurtenissen te luisteren of background-position
te veranderen.
Een vlieg in de zalf: Mobile Safari
Er zijn kanttekeningen bij elk effect, en een belangrijke bij transformaties gaat over het behoud van 3D-effecten voor onderliggende elementen. Als er elementen in de hiërarchie zijn tussen het element met een perspectief en zijn parallaxerende kinderen, wordt het 3D-perspectief "afgevlakt", 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 deze zal de perspective
effectief afvlakken en we verliezen het parallax-effect. De oplossing is in de meeste gevallen redelijk eenvoudig: je voegt transform-style: preserve-3d
toe aan het element, waardoor het eventuele 3D-effecten (zoals onze perspectiefwaarde) propageert die verderop in de boom zijn toegepast.
.parallax-container {
transform-style: preserve-3d;
}
In het geval van Mobile Safari zijn de zaken echter iets ingewikkelder. overflow-y: scroll
naar het containerelement werkt technisch gezien, maar gaat ten koste van het kunnen gooien van het scrollende element. De oplossing is om -webkit-overflow-scrolling: touch
toe te voegen, maar het zal ook het perspective
afvlakken en we krijgen geen parallaxing.
Vanuit het oogpunt van progressieve verbetering is dit waarschijnlijk niet zo'n groot probleem. Als we niet in elke situatie kunnen parallaxen, werkt onze app nog steeds, maar het zou leuk zijn om een oplossing te bedenken.
position: sticky
aan de redding!
Er is in feite enige hulp in de vorm van position: sticky
, die ervoor zorgt dat elementen tijdens het scrollen aan de bovenkant van de viewport of aan een bepaald bovenliggend element kunnen "plakken". De specificatie is, zoals de meeste, behoorlijk fors, maar bevat een nuttig klein juweeltje:
Dit lijkt op het eerste gezicht misschien niet zoveel te betekenen, maar een belangrijk punt in die zin is wanneer het verwijst naar hoe precies de plakkerigheid van een element wordt berekend: "de offset wordt berekend met verwijzing naar de dichtstbijzijnde voorouder met een scrollvak" . Met andere woorden, de afstand die het plakkerige element moet verplaatsen (zodat het lijkt alsof het aan een ander element of de viewport is bevestigd) wordt berekend voordat er andere transformaties worden toegepast, en niet erna . Dit betekent dat, net als bij het schuifvoorbeeld eerder, als de offset is berekend op 300px, er een nieuwe mogelijkheid is om perspectieven (of een andere transformatie) te gebruiken om die offsetwaarde van 300px te manipuleren voordat deze wordt toegepast op vastgezette elementen.
Door position: -webkit-sticky
toe te passen op het parallaxende element, kunnen we het afvlakkingseffect van -webkit-overflow-scrolling: touch
effectief "omkeren". Dit zorgt ervoor dat het parallaxelement verwijst naar de dichtstbijzijnde voorouder met een scrollvak, in dit geval .container
. Vervolgens past de .parallax-container
, net als voorheen, een perspective
toe, waardoor de berekende scroll-offset verandert en een parallax-effect ontstaat.
<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);
}
Dit herstelt het parallax-effect voor Mobile Safari, wat in alle opzichten uitstekend nieuws is!
Kleverige positioneringsvoorbehouden
Er is hier echter een verschil: position: sticky
verandert de parallax-mechanica. Sticky positionering probeert het element 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 de parallax zonder:
- Met
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 een beetje abstract lijkt, kijk dan eens naar deze demo van Robert Flack, die laat zien hoe elementen zich anders gedragen met en zonder sticky positionering. Om het verschil te zien, heb je Chrome Canary (op het moment van schrijven versie 56) of Safari nodig.
Een demo van Robert Flack die laat zien hoe position: sticky
parallax-scrollen beïnvloedt.
Diverse bugs en oplossingen
Zoals met alles zijn er echter nog steeds hobbels en oneffenheden die moeten worden gladgestreken:
- Vaste ondersteuning is inconsistent. Ondersteuning wordt nog steeds geïmplementeerd in Chrome, Edge ontbeert volledig ondersteuning en Firefox vertoont fouten bij het schilderen wanneer sticky 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 "gewoon" in Edge. Edge probeert het scrollen op OS-niveau af te handelen, wat over het algemeen een goede zaak is, maar in dit geval voorkomt het dat het de perspectiefveranderingen tijdens het scrollen detecteert. Om dit op te lossen, kunt u een element met een vaste positie toevoegen, omdat dit Edge lijkt over te schakelen naar een niet-OS-scrollmethode en ervoor zorgt dat er rekening wordt gehouden met perspectiefveranderingen.
- "De inhoud van de pagina is zojuist enorm geworden!" Veel browsers houden rekening met de schaal bij het bepalen hoe groot de inhoud van de pagina is, maar helaas houden Chrome en Safari geen rekening met perspectief . Dus als er bijvoorbeeld een schaal van 3x op een element wordt toegepast, zie je mogelijk schuifbalken en dergelijke, zelfs als het element op 1x staat nadat het
perspective
is toegepast. Het is mogelijk om dit probleem te omzeilen door elementen vanuit de rechterbenedenhoek te schalen (mettransform-origin: bottom right
), wat werkt omdat het ervoor zorgt dat te grote elementen uitgroeien tot het "negatieve gebied" (meestal linksboven) van de afbeelding. schuifbaar gebied; Door de schuifbare gebieden kunt u nooit inhoud in het negatieve gebied zien of ernaar scrollen.
Conclusie
Parallaxen is een leuk effect als het doordacht wordt gebruikt. Zoals u kunt zien, is het mogelijk om het te implementeren op een manier die performant, scroll-gekoppeld en cross-browser is. Omdat het een beetje wiskundig geworstel en een kleine hoeveelheid standaardwerk vereist om het gewenste effect te krijgen, hebben we een kleine helperbibliotheek en voorbeeld samengesteld, die je kunt vinden in onze UI Element Samples GitHub-repository .
Speel mee en laat ons weten hoe het met je gaat.