A scroll container whose edges fade only where there is more to scroll, animated as you scroll.
Installation
Usage
<script setup lang="ts">
import { ScrollFade } from '@animate-ui/vue/primitives/effects/scroll-fade';
</script>
<template>
<ScrollFade class="h-64">
<ul><!-- … --></ul>
</ScrollFade>
</template>orientation="horizontal" fades the left and right edges, "both" all four. With as-child, the child element becomes the scroll container.
To fade a scroll element you don't render yourself (a library's viewport), pass its ref to useScrollFade:
<script setup lang="ts">
import { ref } from 'vue';
import { ScrollAreaViewport } from 'reka-ui';
import { useScrollFade } from '@animate-ui/vue/primitives/effects/scroll-fade';
const viewport = ref<HTMLElement | null>(null);
useScrollFade(viewport, () => ({ size: 32 }));
</script>
<template>
<ScrollAreaViewport ref="viewport"><!-- … --></ScrollAreaViewport>
</template>Animation
- An edge fades only when there is content past it: none at the edge you're at, none at all when nothing overflows. The fade follows the scroll position directly, with no lag: over the first half of
sizescrolled away from an edge (20px by default), that edge goes from sharp to faded, and back as you return. The fade keeps its depth (size); only how much the edge clears changes. - On page load, the fade is already in place: nothing animates in.
- The items shrink and blur into the fade with it: from 1.75 times
sizeaway from a faded edge, each one eases toward 97% (shrink, 0.03) and a 4px blur (blur) as its center nears the edge, following the scroll the same way. Their places are measured when the content changes and once as a scroll starts, never every frame, and they're written once per frame. An item with its ownfilterkeeps it and isn't blurred. The items are the children of the first element holding several (a list's items, through itsul), and a group among them is opened up: aul,ol,role="list"orrole="group", or an element holding one (a section with its title and list), shrinks line by line, never as a whole. They're shrunk with thescaleproperty so their owntransformstays. - It's a
mask-image, so the content itself dissolves: it works on any background, in light and dark. - Scrolling never re-renders Vue: the amounts are motion values written to CSS variables (
--scroll-fade-top,--scroll-fade-bottom,--scroll-fade-left,--scroll-fade-right, from 0 to 1). - When the content changes size without a scroll (items added or removed), the edges ease to their new amount (200ms,
transition). - Under reduced motion, the fade and the blur follow the scroll position the same way, and jump when the content changes size; nothing shrinks.
API Reference
ScrollFade
| Prop | Type | Default |
|---|---|---|
as-child? | boolean | false |
orientation? | 'vertical' | 'horizontal' | 'both' | 'vertical' |
size? | number | 40 |
shrink? | number | 0.03 |
blur? | number | 4 |
transition? | Transition | { duration: 0.2, ease: [0.23, 1, 0.32, 1] } |
enabled? | boolean | true |
...props? | HTMLAttributes | - |
useScrollFade
useScrollFade(ref, options) applies the fade to the element of ref. It takes the options of ScrollFade (orientation, size, shrink, blur, transition, enabled) and returns the motion values of each edge (top, bottom, left, right). It doesn't set the overflow: the element must already scroll.