statecss-animationssveltesveltekitsvelte-5

Use $derived based on current state of self


When modifying the height of a sticky header based on a threshold for the yScroll position, the animation can loop infinitely if scrolling is stopped close to the threshold, as the shrinking header itself reduces the value of yScroll and thereby triggers it to grow again (and so on). I fixed the problem by triggering the animation only if the threshold was crossed including a buffer value.

While the code below works fine, I could not find a way to use $derived instead of $effect. Is this problem solvable using $derived? Am I approaching the issue correctly?

<script lang="ts">
    let yScroll = $state(0);
    let threshold = 50;
    let buffer = 40;
    let isShrunk = $state(false);

    $effect(() => {
        if (yScroll >= threshold + buffer) {
            isShrunk = true;
        } else if (yScroll <= threshold - buffer) {
            isShrunk = false;
        }
    });
</script>

<svelte:window bind:scrollY={yScroll} />

<header class="transition-all duration-500 {isShrunk ? 'h-16' : 'h-32'}">Header</header>

Solution

  • This feels very event-based, so I don't think $derived is the right tool.

    If you describe what was happening before the logic change and after, this should become more apparent:

    Before: "Header is shrunk while scroll offset is smaller than threshold"
    After: "Header is shrunk when scroll offset crosses upper boundary and expanded when scroll offset crosses lower boundary.

    The former describes a consistent condition, the latter two discrete events that have to be detected.

    If you don't like the $effect (which I would say is fine here), you could also use the scroll event instead.

    <svelte:window on:scroll={onScroll} />
    
    function onScroll() {
        if (window.scrollY >= threshold + buffer) {
            isShrunk = true;
        } else if (window.scrollY <= threshold - buffer) {
            isShrunk = false;
        }
    }
    

    Though the function might need to be called once, if the initial state is off.