rune-scroller
v2.2.2
Published
Lightweight, high-performance scroll animations for Svelte 5. 14KB gzipped, zero dependencies.
Maintainers
Readme
⚡ Rune Scroller
Lightweight scroll animations for Svelte 5 — Built with Svelte 5 Runes and IntersectionObserver API.
🚀 Open Source by ludoloops at LeLab.dev 📜 Licensed under MIT
✨ Features
- 5.7KB gzipped (JS+CSS) - Minimal overhead
- Zero dependencies - Pure Svelte 5 + IntersectionObserver
- 14 animations - Fade, Zoom, Flip, Slide, Bounce
- TypeScript support - Full type definitions
- SSR-ready - SvelteKit compatible
- GPU-accelerated - Pure CSS transforms
- Accessible - Respects
prefers-reduced-motion
📦 Installation
npm install rune-scroller🚀 Quick Start
<script>
import runeScroller from 'rune-scroller';
</script>
<!-- Simple animation -->
<div use:runeScroller={{ animation: 'fade-in' }}>
<h2>Animated Heading</h2>
</div>
<!-- With custom duration -->
<div use:runeScroller={{ animation: 'fade-in-up', duration: 1500 }}>
<div class="card">Smooth fade and slide</div>
</div>
<!-- Repeat on every scroll -->
<div use:runeScroller={{ animation: 'bounce-in', repeat: true }}>
<button>Bounces on every scroll</button>
</div>🎨 Available Animations
Fade (5)
fade-in- Simple opacity fadefade-in-up- Fade + move up 300pxfade-in-down- Fade + move down 300pxfade-in-left- Fade + move from right 300pxfade-in-right- Fade + move from left 300px
Zoom (5)
zoom-in- Scale from 0.3 to 1zoom-out- Scale from 2 to 1zoom-in-up- Zoom (0.5→1) + move up 300pxzoom-in-left- Zoom (0.5→1) + move from right 300pxzoom-in-right- Zoom (0.5→1) + move from left 300px
Others (4)
flip- 3D flip on Y-axisflip-x- 3D flip on X-axisslide-rotate- Slide + rotate 10°bounce-in- Bouncy entrance (spring effect)
⚙️ Options
interface RuneScrollerOptions {
animation?: AnimationType // Animation name (default: 'fade-in')
duration?: number // Duration in ms (default: 800)
repeat?: boolean // Repeat on scroll (default: false)
debug?: boolean // Show sentinel as visible line (default: false)
offset?: number // Sentinel offset in px (default: 0, negative = earlier)
onVisible?: (element: HTMLElement) => void // Callback when visible
sentinelColor?: string // Debug sentinel color (e.g. '#ff6b6b')
sentinelId?: string // Custom sentinel ID
}Examples
<!-- Basic -->
<div use:runeScroller={{ animation: 'zoom-in' }}>Content</div>
<!-- Custom duration -->
<div use:runeScroller={{ animation: 'fade-in-up', duration: 1000 }}>Fast</div>
<!-- Repeat mode -->
<div use:runeScroller={{ animation: 'bounce-in', repeat: true }}>Repeats</div>
<!-- Debug mode -->
<div use:runeScroller={{ animation: 'fade-in', debug: true }}>Debug</div>
<!-- Trigger earlier with negative offset -->
<div use:runeScroller={{ animation: 'fade-in-up', offset: -200 }}>
Triggers 200px before element bottom
</div>
<!-- onVisible callback for analytics -->
<div use:runeScroller={{
animation: 'fade-in-up',
onVisible: (el) => {
window.gtag?.('event', 'section_viewed', { id: el.id });
}
}}>
Tracked section
</div>🎯 How It Works
Sentinel-based triggering:
- Invisible 1px sentinel created below your element
- When sentinel enters viewport, animation triggers
- Uses native IntersectionObserver for performance
- Pure CSS animations (GPU-accelerated)
- ResizeObserver auto-repositions sentinel
Why sentinels?
- Accurate timing across all screen sizes
- No complex offset calculations
- Works with animated elements (transforms don't affect observer)
🌐 SSR Compatibility
Works seamlessly with SvelteKit:
<!-- src/routes/+layout.svelte -->
<script>
import runeScroller from 'rune-scroller';
let { children } = $props();
</script>
{@render children()}♿ Accessibility
Respects prefers-reduced-motion:
@media (prefers-reduced-motion: reduce) {
.scroll-animate {
animation: none !important;
opacity: 1 !important;
transform: none !important;
}
}📚 API Reference
// Default export
import runeScroller from "rune-scroller"
// Named exports
import {
useIntersection, // Composable
useIntersectionOnce, // Composable
calculateRootMargin, // Utility
} from "rune-scroller"
// Types
import type { AnimationType, RuneScrollerOptions } from "rune-scroller"📖 Examples
Staggered Animations
<script>
import runeScroller from 'rune-scroller';
const items = ['Item 1', 'Item 2', 'Item 3'];
</script>
{#each items as item, i}
<div use:runeScroller={{
animation: 'fade-in-up',
duration: 800,
style: `--delay: ${i * 100}ms`
}}>
{item}
</div>
{/each}Hero Section
<h1 use:runeScroller={{ animation: 'fade-in-down', duration: 1000 }}>Welcome</h1>
<p use:runeScroller={{ animation: 'fade-in-up', duration: 1200 }}>Subtitle</p>
<button use:runeScroller={{ animation: 'zoom-in', duration: 800 }}>Get Started</button>🔗 Links
- npm: rune-scroller
- GitHub: lelabdev/rune-scroller
- Changelog: CHANGELOG.md
📄 License
MIT © ludoloops
Made with ❤️ by LeLab.dev
