Vanilla JS parallax achtergrond

Het probleem: ik wil de achtergrond van mijn webpagina diepte geven (langzamer laten scrollen), zonder bloat zoals react of jquery, of welk nutteloos framework dan ook hip mag zijn op dit moment.

De oplossing: dit simpele scriptje + html.

Op voorwaarde dat body in ieder geval deze styling heeft: background-image: url(je-plaatje.png); background-attachment: fixed;. Zorg ook dat het script pas wordt geladen in de body.

<body>
    <script>

const factor = 4; // hoe veel keer trager?

var scrollY = document.documentElement.scrollTop || window.pageYOffset;
document.body.style.backgroundPositionY = (-scrollY/factor) + "px";
window.requestAnimationFrame(function(){});

window.addEventListener('scroll', function(){
    scrollY = document.documentElement.scrollTop || window.pageYOffset;
    document.body.style.backgroundPositionY = (-scrollY/factor) + "px";
    window.requestAnimationFrame(function(){});
});

    </script>

    <!-- De content -->
</body>

Voor andere dingen dan alleen plaatjes

Jazeker, dat kan! document.body moet je vervangen door je eigen element, b.v. document.getElementById("parallaxElement"). Vervolgens moet je dit element ook de styling position: fixed geven. Je kan ook de browser-berekende positie van een element lezen, dan het element fixed maken, en dan als offset toevoegen aan de background-position, of het element zelf offsetten met top en left. Ik zou hier dan toch wél naar een framework neigen.

Toelichting van het script:

Laten we beginnen bij de 4 na laatste regel, window.addEventListener('scroll', function(){. Die bind een anonieme functie aan het scroll event van je browser. Elke keer dat je scrollt in de pagina, wordt die anonieme functie dus aangeroepen.

Op de volgende regel bepalen we hoe veel pixels je reeds naar beneden hebt gescrolld. Eerst kijken we wat scrollTop is. Als dat niks is (de property bestaat b.v. niet, geen browserondersteuning) dan gebruiken we in plaats daarvan de pagyYOffset. Die werkt relatief trager, dus daarom eerst scrollTop proberen.

Op regel daarna delen we dit getal door de vertragingsfactor, en maken we het negatief. Dit doen we omdat de achtergrond als het ware mee scrollt door de background-attachment: fixed. We moeten hem dus iets omhoog (negatieve y-richting) corrigeren, evenredig met scrollY/factor. Voor de rest zetten we er "px" achter zodat de browser weet hoe hij het getal moet interpreteren, en dan passen we de backgroundPositionY van body aan.

Ten slotte vertellen we de browser met requestAnimationFrame() dat we nu éérst een nieuwe paint willen maken. Events gebeuren namelijk parallel. Zo krijgen we niet per ongeluk het probleem dat er op t=0 een scrollEvent "s1" wordt verzonden, die toevallig 50 microseconden duurt, en op t = 10 een nieuwe scrollEvent "s2" wordt verzonden die 20 microseconden duurt. Dan komt s2 aan vóór s1, en dus gaat de achtergrond eerst naar de juiste positie, maar 30 microseconden later gaat hij onbedoeld een stukje, waarschijnlijk niet meer dan een kwartpixel, naar boven. Deze aantallen zijn natuurlijk zo miniscuul en onmerkbaar dat het helemaal nergens op slaat om hier rekening mee te houden, maar op tragere computers kun je gerust met miliseconden rekenen, en dan is het zeker merkbaar.

De reden dat ik een lege functie als argument voor requestAnimationFrame gebruik is dat ik niet anders kan. Hij heeft namelijk 1 verplicht argument, met type functie: de callback voor wanneer de het frame getekend is, maar die hebben we hier niet nodig. Het is wel nuttig in iets recursiefs als dit:

function renderNextFrame() {
    window.requestAnimationFrame(renderNextFrame);
}

Nu is er nog één raadsel: waarom voer ik de functie eenmalig uit als het script begint? Nou, als je de pagina laadt met een bepaalde focus (url.nl/pagina#focus), dan kom je direct bij het element met id="#focus" uit, maar daar wordt geen scroll event voor afgehandeld door de browser. Dus als je gaat scrollen vanaf een beginpositie anders dan y=0, springt de achtergrond ineens naar de juiste positie, wat lelijk is, dus zorgen we ervoor dat de positie van het achtergrondplaatje eenmalig gecorrigeerd wordt direct na het laden.

Waarom gebruik ik geen centrale functie?

Nu vraag je je misschien af waarom ik uberhaupt een anonieme functie gebruik, en dan ook nog eens de 3 regels gewoon kopiëer, in plaats van alles in 1 functie te zetten en die aan te roepen in het begin en te binden aan het event. Snelheid! Om de een of andere reden stottert hij als ik een centrale functie gebruik, dus daarom doe ik het zo.

2019-06-21 in blog #programmeren #Javascript #parallax