Vue, Scroll Handler Animation, debounce (+ optional Nuxt)

Ich brauchte für eine Component einen Scroll-Handler, welcher ein Element abhängig von der Scroll-Position des Elementes bewegt.

In dem Component sollte ein Bild langsam von links nach rechts sliden, je nachdem, wie weit das Component vom unterem/oberem Bildschirmrand entfernt ist; ist die Component ganz unten, sollte das Bild ganz links sein, ist die Component ganz oben im Browser, sollte das Bild langsam aus dem linken Bildschirmrand verschwinden.

Aus Performancegründen habe ich ebenfalls debounce genutzt; debounce ruft man mit einer Funktion als Parameter auf, und es kümmert sich darum, dass diese Funktion nur ein mal alle x Millisekunden aufgerufen wird, selbst wenn die mehrmals gestartet werden soll. Das macht beispielsweise aus 112.983 Scroll-Events die Sekunde nur 200 (bei einem Timeout von 5) – und ist gut für die Performance. Wer das nicht benötigt, nimmt einfach den debounce-Wrapper aus unten stehender Component weg.

<script setup>
// Die Imports von vue sind bei nuxt nicht notwendig

import { ref, onMounted, onUnmounted } from 'vue';
// Der hier allerdings schon; den kann nuxt nicht automatisch resolven
import debounce from 'debounce';

// Der Wrapper um das komplette Element. Wird als ref unten eingebunden, damit wir das Offset von dem zum Bildschirmrand berechnen können
const theSpacer = ref(null);

// paddingLeft des Elementes, welches bewegt werden soll
const position = ref(0);

const calculatePosition = debounce(() => {

    // Offset nach oben berechnen; spuckt einen float-Wert zwischen 0 (unter dem unterem Bildschirmrand) und 1 (über dem oberen Bildschirmrand) aus
    var scrollTop = (window.pageYOffset || window.scrollTop || 0) + window.innerHeight - theSpacer.value.offsetTop;
    scrollTop = scrollTop < 0 ? 0 : scrollTop;
    var scrollPercent = scrollTop / window.innerHeight || 0;
    if(scrollPercent < 0) scrollPercent = 0;
    if(scrollPercent > 1) scrollPercent = 1;

    
    // Nun das Offset nach links berechnen; Faktor, mal Breite
    position.value = (scrollPercent * window.innerWidth) + 'px';
}, 5);

onMounted(() => {
    // Bei Nuxt folgende Zeile einfügen:
    // if(process.server) return;

    // Bei Mount der Component den Scroll-Listener hinzufügen
    window.addEventListener('scroll', calculatePosition);
    
    calculatePosition();
});

onUnmounted(() => {
    // Bei Nuxt folgende Zeile einfügen:
    // if(process.server) return;
    // Bei UnMount der Component den Scroll-Listener entfernen
    window.removeEventListener('scroll', calculatePosition);
});
</script>

<template>
    <div class="wrapper">
        <div class="spacer" ref="theSpacer">

            <!-- das folgende Bild wird bewegt, indem der paddingLeft gesetzt wird -->
            <img src="/images/moving-spacer/moving-image.png" :style="{'paddingLeft': position}" />
        </div>
    </div>
</template>

<style scoped>
.wrapper {
    padding-top: 15px;
    padding-bottom: 15px;
    background-image: url('/images/moving-spacer/background.png');
    background-position: center center;
    background-repeat: repeat;
    overflow: hidden;
}

.spacer {
    width: 100%;
    height: 80px;
}

img {
    max-height: 100%;

    /* Dadurch bewegt sich das Bild etwas langsamer hin und her, wenn gescrollt wird, und wird nicht "teleportiert" */
    transition: 0.5s all ease;
}
</style>

Als kleiner Hinweis zu diesem Component: in meinem Projekt benutze ich nuxt, welches diverse Sachen automatisch importiert. Ich habe diese Imports händisch hinzugefügt; sollte allerdings noch irgendwo irgendwas fehlen, sorry for that, das sollte sich ja dennoch relativ schnell finden lassen.

Kategorie: Vue.JS

tino-ruge.de wird tino-kuptz.de

Im Laufe des Jahres 2024 wird dieser Blog umziehen. Alle Inhalte werden 1:1 weitergeleitet, nix geht verloren. Neue Domain, alter Autor, alter Inhalt.