rune-scroller 2.2.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,14 +1,12 @@
1
- # ⚡ Rune Scroller - Full Reference
2
-
3
- **📚 Complete API Reference** — Detailed documentation for all features and options.
4
-
5
- ---
1
+ # ⚡ Rune Scroller
6
2
 
7
3
  <div align="center">
8
4
  <img src="./logo.png" alt="Rune Scroller Logo" width="200" />
9
5
  </div>
10
6
 
11
- **Lightweight scroll animations for Svelte 5** — Built with Svelte 5 Runes and IntersectionObserver API.
7
+ **Lightweight scroll animations. AOS replacement. Works everywhere.**
8
+
9
+ Built with native IntersectionObserver — zero JS on scroll, GPU-accelerated, ~5.6KB gzipped.
12
10
 
13
11
  > 🚀 **Open Source** by [ludoloops](https://github.com/ludoloops) at [LeLab.dev](https://lelab.dev)
14
12
  > 📜 Licensed under **MIT**
@@ -24,517 +22,263 @@
24
22
 
25
23
  ---
26
24
 
27
- ## Features
25
+ ## 🚀 Quick Start
28
26
 
29
- - **14KB gzipped** (47KB uncompressed) - Minimal overhead, optimized for production
30
- - **Zero dependencies** - Pure Svelte 5 + IntersectionObserver
31
- - **14 animations** - Fade, Zoom, Flip, Slide, Bounce variants
32
- - **Full TypeScript support** - Type definitions generated from JSDoc
33
- - **SSR-ready** - SvelteKit compatible
34
- - **GPU-accelerated** - Pure CSS transforms
35
- - **Accessible** - Respects `prefers-reduced-motion`
36
- - **v2.0.0 New** - `onVisible` callback, ResizeObserver support, animation validation, sentinel customization
37
- - **✨ Latest** - `useIntersection` migrated to Svelte 5 `$effect` rune for better lifecycle management
38
- - **🚀 Bundle optimized** - CSS with custom properties, production build minification
39
- - **🚀 v2.2.0** - Cache CSS check to eliminate 99% of reflows
27
+ ### Any framework Svelte, React, Vue, Angular, Vanilla JS
40
28
 
41
- ---
42
-
43
- ## 🚀 Performance
44
-
45
- ### Cache CSS Validation (v2.2.0)
46
-
47
- **Problem:**
48
- - `checkAndWarnIfCSSNotLoaded()` was called for EVERY element
49
- - Each call did:
50
- - `document.createElement('div')`
51
- - `document.body.appendChild(test)`
52
- - `getComputedStyle(test)` ⚠️ **Expensive!** Forces full page reflow
53
- - `test.remove()`
54
- - For 100 animated elements = **100 reflows + 100 DOM operations**
55
-
56
- **Solution:**
57
- ```javascript
58
- // Cache to check only once per page load
59
- let cssCheckResult = null;
60
-
61
- export function checkAndWarnIfCSSNotLoaded() {
62
- if (cssCheckResult !== null) return cssCheckResult;
63
- // ... expensive check ...
64
- cssCheckResult = hasAnimation;
65
- return hasAnimation;
66
- }
29
+ ```bash
30
+ npm install rune-scroller
67
31
  ```
68
32
 
69
- **Impact:**
70
- - Validation runs ONLY ONCE per page load
71
- - Eliminates layout thrashing from repeated `getComputedStyle()` calls
72
- - For 100 elements: **99 fewer reflows** (100 → 1)
73
- - Zero memory overhead (single boolean)
74
-
75
- ### Current Performance Metrics
76
-
77
- | Metric | Value |
78
- |---------|--------|
79
- | **Bundle size** | 12.4KB (compressed), 40.3KB (unpacked) |
80
- | **Initialization** | ~1-2ms per element |
81
- | **Observer callback** | <0.5ms per frame |
82
- | **CSS validation** | ~0.1ms total (v2.2.0, with cache) |
83
- | **Memory per observer** | ~1.2KB |
84
- | **Animation performance** | 60fps maintained |
85
- | **Memory leaks** | 0 detected |
86
-
87
- ### Why Performance Matters
88
-
89
- **Layout Thrashing:**
90
- - Synchronous reflows block the main thread
91
- - Each reflow can take 10-20ms
92
- - For 100 elements = **1-2 seconds blocked**
93
- - User sees stuttering/jank while scrolling
94
-
95
- **Solution:**
96
- - Cache = 1 reflow instead of N
97
- - 99% improvement on pages with many animations
98
- - Smoother scrolling, better UX
99
-
100
- ### Optimized Code Patterns
101
-
102
- **IntersectionObserver:**
103
- - Native API (no scroll listeners)
104
- - Fast callback (<0.5ms per frame)
105
- - No debounce needed (browser handles this efficiently)
106
-
107
- **CSS Animations:**
108
- - Transforms only (GPU-accelerated)
109
- - No layout/repaint during animation
110
- - `will-change` on visible elements only
111
-
112
- **DOM Operations:**
113
- - `insertAdjacentElement('beforebegin')` instead of `insertBefore`
114
- - `offsetHeight` instead of `getBoundingClientRect()` (avoids transform issues)
115
- - Complete cleanup on destroy
116
-
117
- **Memory Management:**
118
- - All observers disconnected
119
- - Sentinel and wrapper removed
120
- - State prevents double-disconnects
121
- - 0 memory leaks detected (121/121 tests)
122
-
123
- ### Future Considerations
124
-
125
- **1. `will-change` Timing**
126
- - Currently: `.is-visible { will-change: transform, opacity; }`
127
- - Trade-off: Stays active after animation (consumes GPU memory)
128
- - Consideration: Use `transitionend` event to remove `will-change`
129
- - Recommendation: Keep current (GPU memory is cheap)
130
-
131
- **2. Threshold Tuning**
132
- - Current: `threshold: 0` (triggers as soon as 1px is visible)
133
- - Alternative: `threshold: 0.1` or `threshold: 0.25`
134
- - Trade-off: Higher threshold = later trigger = smoother stagger
135
- - Recommendation: Keep `threshold: 0` for immediate feedback
136
-
137
- **3. requestIdleCallback**
138
- - Potential: Defer non-critical setup to browser idle time
139
- - Trade-off: Complex to implement, marginal benefit
140
- - Recommendation: Not needed (current performance is excellent)
141
-
142
- **4. Testing on Low-End Devices**
143
- - Test on mobile phones, older browsers
144
- - Use DevTools CPU throttling
145
- - Consider Lighthouse/Puppeteer for automated testing
146
- - Ensure 60fps maintained on real devices
147
-
148
- ### What NOT to Optimize
149
-
150
- **Anti-patterns to avoid:**
151
-
152
- 1. ❌ **Premature optimization**
153
- - Don't optimize without measurements
154
- - Profile first, optimize later
155
- - "Premature optimization is the root of all evil"
156
-
157
- 2. ❌ **Over-engineering**
158
- - Complex solutions for small gains
159
- - Keep it simple when possible
160
- - Don't sacrifice readability for micro-optimizations
161
-
162
- 3. ❌ **Breaking performance for size**
163
- - Bundle size matters (12.4KB is excellent)
164
- - Don't add huge dependencies for minor improvements
165
-
166
- 4. ❌ **Optimizing unused paths**
167
- - Focus on hot paths (element creation, scroll, intersection)
168
- - Cold paths (initialization, destroy) less critical
169
-
170
- 5. ❌ **Sacrificing maintainability**
171
- - Don't sacrifice code clarity for micro-optimizations
172
- - Comments should explain WHY, not just WHAT
173
- - Keep code simple and understandable
174
-
175
- ### Performance Testing
176
-
177
- **Recommended approach:**
178
- 1. Create a benchmark with 100-1000 animated elements
179
- 2. Measure: initialization, first animation, scroll performance, cleanup
180
- 3. Profile with DevTools Performance tab
181
- 4. Test on real pages (not just benchmarks)
182
- 5. Verify 60fps is maintained during scroll
183
-
184
- **Tools:**
185
- - Chrome DevTools Performance tab
186
- - Firefox Performance Profiler
187
- - Web Inspector (Safari)
188
- - Lighthouse (PageSpeed, accessibility, best practices)
189
-
190
- ---
191
-
192
- ## 📦 Installation
33
+ ```js
34
+ import AOS from "rune-scroller/aos";
35
+ AOS.init();
36
+ ```
193
37
 
194
- ```bash
195
- npm install rune-scroller
196
- # or
197
- pnpm add rune-scroller
198
- # or
199
- yarn add rune-scroller
38
+ ```html
39
+ <div data-aos="fade-up" data-aos-duration="800">Animated</div>
40
+ <div data-aos="zoom-in" data-aos-delay="200">Delayed zoom</div>
200
41
  ```
201
42
 
202
- ---
43
+ That's it. Same API as AOS. Works everywhere.
203
44
 
204
- ## 🚀 Quick Start
45
+ ### Svelte (native action)
205
46
 
206
47
  ```svelte
207
48
  <script>
208
- import runeScroller from 'rune-scroller';
49
+ import rs from 'rune-scroller';
209
50
  </script>
210
51
 
211
- <!-- Simple animation -->
212
- <div use:runeScroller={{ animation: 'fade-in' }}>
213
- <h2>Animated Heading</h2>
214
- </div>
215
-
216
- <!-- With custom duration -->
217
- <div use:runeScroller={{ animation: 'fade-in-up', duration: 1500 }}>
218
- <div class="card">Smooth fade and slide</div>
219
- </div>
220
-
221
- <!-- Repeat on every scroll -->
222
- <div use:runeScroller={{ animation: 'bounce-in', repeat: true }}>
223
- <button>Bounces on every scroll</button>
224
- </div>
52
+ <div use:rs={{ animation: 'fade-up' }}>Animates on scroll</div>
225
53
  ```
226
54
 
227
- That's it! The CSS animations are included automatically when you import rune-scroller.
55
+ ### React
228
56
 
229
- ### Option 2: Manual CSS Import
57
+ ````jsx
58
+ import { useEffect } from "react";
59
+ ### React (not tested — should work)
230
60
 
231
- For fine-grained control, import CSS manually:
61
+ ```jsx
62
+ import { useEffect } from 'react';
63
+ import AOS from 'rune-scroller/aos';
232
64
 
233
- **Step 1: Import CSS in your root layout (recommended for SvelteKit):**
65
+ function App() {
66
+ useEffect(() => { AOS.init(); }, []);
67
+ return (
68
+ <>
69
+ <h1 data-aos="fade-down">Welcome</h1>
70
+ <p data-aos="fade-up" data-aos-delay="200">Subtitle</p>
71
+ </>
72
+ );
73
+ }
74
+ ````
234
75
 
235
- ```svelte
236
- <!-- src/routes/+layout.svelte -->
237
- <script>
238
- import 'rune-scroller/animations.css';
239
- let { children } = $props();
76
+ ### Vue (not tested — should work)
77
+
78
+ ```vue
79
+ <script setup>
80
+ import { onMounted } from "vue";
81
+ import AOS from "rune-scroller/aos";
82
+ onMounted(() => AOS.init());
240
83
  </script>
241
84
 
242
- {@render children()}
85
+ <template>
86
+ <div data-aos="fade-up">Animated</div>
87
+ </template>
243
88
  ```
244
89
 
245
- **Or import in each component:**
90
+ ### Angular (not tested — should work)
246
91
 
247
- ```svelte
248
- <script>
249
- import runeScroller from 'rune-scroller';
250
- import 'rune-scroller/animations.css';
251
- </script>
92
+ ```typescript
93
+ // app.component.ts
94
+ import { Component, OnInit } from "@angular/core";
95
+ import AOS from "rune-scroller/aos";
96
+
97
+ @Component({ selector: "app-root", templateUrl: "./app.component.html" })
98
+ export class AppComponent implements OnInit {
99
+ ngOnInit() {
100
+ AOS.init();
101
+ }
102
+ }
252
103
  ```
253
104
 
254
- **Step 2: Use the animations**
105
+ ```html
106
+ <!-- app.component.html -->
107
+ <div data-aos="fade-up">Animated</div>
108
+ ```
255
109
 
256
- ```svelte
257
- <script>
258
- import runeScroller from 'rune-scroller';
259
- // CSS already imported in layout or above
110
+ ### CDN (not tested — should work)
111
+
112
+ ```html
113
+ <script type="module">
114
+ import AOS from "https://esm.sh/rune-scroller/aos";
115
+ AOS.init();
260
116
  </script>
261
117
 
262
- <div use:runeScroller={{ animation: 'fade-in' }}>
263
- Animated content
264
- </div>
118
+ <div data-aos="fade-up">Works without any build step</div>
265
119
  ```
266
120
 
267
121
  ---
268
122
 
269
- ## 🎨 Available Animations
270
-
271
- ### Fade (5)
272
- - `fade-in` - Simple opacity fade
273
- - `fade-in-up` - Fade + move up 300px
274
- - `fade-in-down` - Fade + move down 300px
275
- - `fade-in-left` - Fade + move from right 300px
276
- - `fade-in-right` - Fade + move from left 300px
277
-
278
- ### Zoom (5)
279
- - `zoom-in` - Scale from 0.3 to 1
280
- - `zoom-out` - Scale from 2 to 1
281
- - `zoom-in-up` - Zoom (0.5→1) + move up 300px
282
- - `zoom-in-left` - Zoom (0.5→1) + move from right 300px
283
- - `zoom-in-right` - Zoom (0.5→1) + move from left 300px
123
+ ## Features
284
124
 
285
- ### Others (4)
286
- - `flip` - 3D flip on Y-axis
287
- - `flip-x` - 3D flip on X-axis
288
- - `slide-rotate` - Slide + rotate 10°
289
- - `bounce-in` - Bouncy entrance (spring effect)
125
+ - **Framework agnostic** — Svelte, React, Vue, Angular, Vanilla JS, CDN
126
+ - **AOS drop-in** — Same `data-aos` attributes, same `init()` API
127
+ - **Zero dependencies** Pure JS + native IntersectionObserver
128
+ - **~5.6KB gzipped** Smaller than AOS (6.9KB)
129
+ - **37 animations** Fade, Zoom, Flip, Slide, Bounce
130
+ - **Zero JS on scroll** — Browser handles detection natively
131
+ - **TypeScript support** — Full type definitions
132
+ - **SSR-ready** — SvelteKit, Next.js, Nuxt compatible
133
+ - **GPU-accelerated** — CSS transforms via `translate3d`
134
+ - **Accessible** — Respects `prefers-reduced-motion`
135
+ - **No wrapper divs** — Your layouts stay intact
290
136
 
291
137
  ---
292
138
 
293
- ## ⚙️ Options
139
+ ### AOS vs rune-scroller
294
140
 
295
- ```typescript
296
- interface RuneScrollerOptions {
297
- animation?: AnimationType; // Animation name (default: 'fade-in')
298
- duration?: number; // Duration in ms (default: 800)
299
- repeat?: boolean; // Repeat on scroll (default: false)
300
- debug?: boolean; // Show sentinel as visible line (default: false)
301
- offset?: number; // Sentinel offset in px (default: 0, negative = above)
302
- onVisible?: (element: HTMLElement) => void; // Callback when animation triggers (v2.0.0+)
303
- sentinelColor?: string; // Sentinel debug color, e.g. '#ff6b6b' (v2.0.0+)
304
- sentinelId?: string; // Custom ID for sentinel identification (v2.0.0+)
305
- }
306
- ```
141
+ | | rune-scroller | AOS |
142
+ | ------------------------- | ---------------------------------------------- | ------------------------------------------ |
143
+ | **Bundle size (gzipped)** | **~5.6KB** JS+CSS | ~6.9KB JS+CSS |
144
+ | **Dependencies** | **0** | lodash.throttle, lodash.debounce |
145
+ | **Scroll detection** | **IntersectionObserver** (native, C++) | Scroll event + throttle (JS) |
146
+ | **Per-scroll cost** | **0** browser handles it | Iterates ALL elements every 99ms |
147
+ | **Layout reads** | **1 per element** (init only) | `offsetParent` loop per element per scroll |
148
+ | **Resize handling** | **ResizeObserver** (native) | debounced scroll recalc |
149
+ | **100 animated elements** | **~0ms per scroll** | ~2-5ms per scroll (layout thrashing) |
150
+ | **Animations** | 30 | 28 |
151
+ | **Framework** | **Any** (Svelte, React, Vue, Angular, Vanilla) | Vanilla JS only |
307
152
 
308
- ### Option Details
153
+ The key difference: **AOS runs JavaScript on every scroll event** for every element. rune-scroller delegates detection to the browser's native IntersectionObserver — zero JS execution until an element actually enters the viewport.
309
154
 
310
- - **`animation`** - Type of animation to play. Choose from 14 built-in animations listed above. Invalid animations automatically fallback to 'fade-in' with a console warning.
311
- - **`duration`** - How long the animation lasts in milliseconds (default: 800ms).
312
- - **`repeat`** - If `true`, animation plays every time sentinel enters viewport. If `false`, plays only once.
313
- - **`debug`** - If `true`, displays the sentinel element as a visible line below your element. Useful for seeing exactly when animations trigger. Default color is cyan (#00e0ff), customize with `sentinelColor`.
314
- - **`offset`** - Offset of the sentinel in pixels. Positive values move sentinel down (delays animation), negative values move it up (triggers earlier). Useful for large elements where you want animation to trigger before the entire element is visible.
315
- - **`onVisible`** *(v2.0.0+)* - Callback function triggered when the animation becomes visible. Receives the animated element as parameter. Useful for analytics, lazy loading, or triggering custom effects.
316
- - **`sentinelColor`** *(v2.0.0+)* - Customize the debug sentinel color (e.g., '#ff6b6b' for red). Only visible when `debug: true`. Useful for distinguishing multiple sentinels on the same page.
317
- - **`sentinelId`** *(v2.0.0+)* - Set a custom ID for the sentinel element. If not provided, an auto-ID is generated (`sentinel-1`, `sentinel-2`, etc.). Useful for identifying sentinels in DevTools and tracking which element owns which sentinel.
155
+ ---
318
156
 
319
- ### Examples
157
+ ## 🎨 Available Animations (30)
320
158
 
321
- ```svelte
322
- <!-- Basic -->
323
- <div use:runeScroller={{ animation: 'zoom-in' }}>
324
- Content
325
- </div>
159
+ ### Fade (10)
326
160
 
327
- <!-- Custom duration -->
328
- <div use:runeScroller={{ animation: 'fade-in-up', duration: 1000 }}>
329
- Fast animation
330
- </div>
161
+ - `fade` Simple opacity fade
162
+ - `fade-up` / `fade-down` / `fade-left` / `fade-right` — Fade + translate
163
+ - `fade-up-right` / `fade-up-left` / `fade-down-right` / `fade-down-left` — Diagonal fades
331
164
 
332
- <!-- Repeat mode -->
333
- <div use:runeScroller={{ animation: 'bounce-in', repeat: true }}>
334
- Repeats every time you scroll
335
- </div>
165
+ ### Zoom (10)
336
166
 
337
- <!-- Debug mode - shows cyan line marking sentinel position -->
338
- <div use:runeScroller={{ animation: 'fade-in', debug: true }}>
339
- The cyan line below this shows when animation will trigger
340
- </div>
167
+ - `zoom-in` / `zoom-out` Scale in/out
168
+ - `zoom-in-up` / `zoom-in-down` / `zoom-in-left` / `zoom-in-right` — Zoom + translate
169
+ - `zoom-out-up` / `zoom-out-down` / `zoom-out-left` / `zoom-out-right` Zoom out + translate
341
170
 
342
- <!-- Multiple options -->
343
- <div use:runeScroller={{
344
- animation: 'fade-in-up',
345
- duration: 1200,
346
- repeat: true,
347
- debug: true
348
- }}>
349
- Full featured example
350
- </div>
171
+ ### Slide (4)
351
172
 
352
- <!-- Large element - trigger animation earlier with negative offset -->
353
- <div use:runeScroller={{
354
- animation: 'fade-in-up',
355
- offset: -200 // Trigger 200px before element bottom
356
- }}>
357
- Large content that needs early triggering
358
- </div>
173
+ - `slide-up` / `slide-down` / `slide-left` / `slide-right` Slide from off-screen
359
174
 
360
- <!-- Delay animation by moving sentinel down -->
361
- <div use:runeScroller={{
362
- animation: 'zoom-in',
363
- offset: 300 // Trigger 300px after element bottom
364
- }}>
365
- Content with delayed animation
366
- </div>
175
+ ### Flip (4)
367
176
 
368
- <!-- v2.0.0: onVisible callback for analytics tracking -->
369
- <div use:runeScroller={{
370
- animation: 'fade-in-up',
371
- onVisible: (el) => {
372
- console.log('Animation visible!', el);
373
- // Track analytics, load images, trigger API calls, etc.
374
- window.gtag?.('event', 'animation_visible', { element: el.id });
375
- }
376
- }}>
377
- Tracked animation
378
- </div>
177
+ - `flip-left` / `flip-right` 3D flip on Y-axis
178
+ - `flip-up` / `flip-down` — 3D flip on X-axis
379
179
 
380
- <!-- v2.0.0: Custom sentinel color for debugging -->
381
- <div use:runeScroller={{
382
- animation: 'fade-in',
383
- debug: true,
384
- sentinelColor: '#ff6b6b' // Red instead of default cyan
385
- }}>
386
- Red debug sentinel
387
- </div>
180
+ ### Special (2)
388
181
 
389
- <!-- v2.0.0: Custom sentinel ID for identification -->
390
- <div use:runeScroller={{
391
- animation: 'zoom-in',
392
- sentinelId: 'hero-zoom',
393
- debug: true
394
- }}>
395
- Identified sentinel (shows "hero-zoom" in debug mode)
396
- </div>
182
+ - `slide-rotate` Slide + rotate
183
+ - `bounce-in` — Bouncy spring entrance
397
184
 
398
- <!-- v2.0.0: Auto-ID (sentinel-1, sentinel-2, etc) -->
399
- <div use:runeScroller={{
400
- animation: 'fade-in-up',
401
- debug: true
402
- // sentinelId omitted → auto generates "sentinel-1", "sentinel-2", etc
403
- }}>
404
- Auto-identified sentinel
405
- </div>
406
- ```
185
+ ### Customizable distance
407
186
 
408
- ---
187
+ All animations use the `--rs-distance` CSS variable (default: `100px`):
409
188
 
410
- ## 🎯 How It Works
411
-
412
- Rune Scroller uses **sentinel-based triggering**:
413
-
414
- 1. An invisible 1px sentinel element is created below your element
415
- 2. When the sentinel enters the viewport, animation triggers
416
- 3. This ensures precise timing regardless of element size
417
- 4. Uses native IntersectionObserver for performance
418
- 5. Pure CSS animations (GPU-accelerated)
419
- 6. *(v2.0.0)* Sentinel automatically repositions on element resize via ResizeObserver
420
-
421
- **Why sentinels?**
422
- - Accurate timing across all screen sizes
423
- - No complex offset calculations
424
- - Handles staggered animations naturally
425
- - Sentinel stays fixed while element animates (no observer confusion with transforms)
426
-
427
- **Automatic ResizeObserver** *(v2.0.0+)*
428
- - Sentinel repositions automatically when element resizes
429
- - Works with responsive layouts and dynamic content
430
- - No configuration needed—it just works
189
+ ```html
190
+ <div data-aos="fade-up" style="--rs-distance: 200px">Farther slide</div>
191
+ ```
431
192
 
432
193
  ---
433
194
 
434
- ## 🌐 SSR Compatibility
195
+ ## ⚙️ Options
435
196
 
436
- Works seamlessly with SvelteKit. Simply import rune-scroller in your root layout:
197
+ ### AOS Mode (data attributes)
198
+
199
+ | Attribute | Example | Description |
200
+ | ------------------- | --------------- | -------------------------- |
201
+ | `data-aos` | `"fade-up"` | Animation name |
202
+ | `data-aos-duration` | `"800"` | Duration in ms |
203
+ | `data-aos-delay` | `"200"` | Delay in ms |
204
+ | `data-aos-easing` | `"ease-in-out"` | CSS timing function |
205
+ | `data-aos-offset` | `"120"` | Trigger offset in px |
206
+ | `data-aos-once` | `"true"` | Animate only once |
207
+ | `data-aos-mirror` | `"true"` | Animate on scroll away too |
208
+
209
+ ### AOS init options
210
+
211
+ ```js
212
+ AOS.init({
213
+ offset: 120,
214
+ duration: 400,
215
+ delay: 0,
216
+ easing: "ease",
217
+ once: false,
218
+ mirror: false,
219
+ startEvent: "DOMContentLoaded",
220
+ });
221
+ ```
437
222
 
438
- ```svelte
439
- <!-- src/routes/+layout.svelte -->
440
- <script>
441
- import runeScroller from 'rune-scroller';
442
- let { children } = $props();
443
- </script>
223
+ ### Svelte Action options
444
224
 
445
- {@render children()}
225
+ ```typescript
226
+ interface RuneScrollerOptions {
227
+ animation?: AnimationType; // default: 'fade-up'
228
+ duration?: number; // default: 400
229
+ delay?: number; // default: 0
230
+ easing?: string; // default: 'ease'
231
+ repeat?: boolean; // default: false
232
+ debug?: boolean;
233
+ offset?: number; // negative = earlier trigger
234
+ onVisible?: (el: HTMLElement) => void;
235
+ sentinelColor?: string;
236
+ sentinelId?: string;
237
+ }
446
238
  ```
447
239
 
448
- Then use animations anywhere in your app:
240
+ ---
449
241
 
450
- ```svelte
451
- <!-- src/routes/+page.svelte -->
452
- <script>
453
- import runeScroller from 'rune-scroller';
454
- </script>
242
+ ## 🎯 How It Works
455
243
 
456
- <!-- No special handling needed -->
457
- <div use:runeScroller={{ animation: 'fade-in-up' }}>
458
- Works in SvelteKit SSR!
459
- </div>
460
- ```
244
+ 1. Invisible 1px sentinel appended as child of the animated element
245
+ 2. When sentinel enters viewport, animation triggers via IntersectionObserver
246
+ 3. Pure CSS transitions (GPU-accelerated via `translate3d`)
247
+ 4. ResizeObserver auto-repositions sentinel
461
248
 
462
- The library checks for browser environment and gracefully handles server-side rendering.
249
+ **No wrapper divs** the element itself becomes the positioning context. Your flex/grid layouts stay intact.
463
250
 
464
251
  ---
465
252
 
466
253
  ## ♿ Accessibility
467
254
 
468
- Respects `prefers-reduced-motion`:
469
-
470
- ```css
471
- /* In animations.css */
472
- @media (prefers-reduced-motion: reduce) {
473
- .scroll-animate {
474
- animation: none !important;
475
- opacity: 1 !important;
476
- transform: none !important;
477
- }
478
- }
479
- ```
480
-
481
- Users who prefer reduced motion will see content without animations.
255
+ Respects `prefers-reduced-motion` — animations are disabled automatically.
482
256
 
483
257
  ---
484
258
 
485
259
  ## 📚 API Reference
486
- ### Public API
487
-
488
- Rune Scroller exports a **single action-based API** (no components):
489
-
490
- 1. **`runeScroller`** (default) - Sentinel-based, simple, powerful
491
-
492
- **Why actions instead of components?**
493
- - Actions are lightweight directives
494
- - No DOM wrapper overhead
495
- - Better performance
496
- - More flexible
497
-
498
- ### Main Export
499
260
 
500
261
  ```typescript
501
- // CSS is automatically included
502
- import runeScroller from 'rune-scroller';
262
+ // Framework agnostic (AOS mode)
263
+ import AOS from "rune-scroller/aos";
264
+ AOS.init();
265
+ AOS.refresh();
266
+ AOS.refreshHard();
267
+
268
+ // Svelte action (default)
269
+ import rs from "rune-scroller";
503
270
 
504
271
  // Named exports
505
272
  import {
506
- useIntersection, // Composable
507
- useIntersectionOnce, // Composable
508
- calculateRootMargin // Utility
509
- } from 'rune-scroller';
273
+ runeScroller,
274
+ useIntersection,
275
+ useIntersectionOnce,
276
+ calculateRootMargin,
277
+ ANIMATION_TYPES,
278
+ } from "rune-scroller";
510
279
 
511
280
  // Types
512
- import type {
513
- AnimationType,
514
- RuneScrollerOptions,
515
- IntersectionOptions,
516
- UseIntersectionReturn
517
- } from 'rune-scroller';
518
- ```
519
-
520
- ### TypeScript Types
521
-
522
- ```typescript
523
- type AnimationType =
524
- | 'fade-in' | 'fade-in-up' | 'fade-in-down' | 'fade-in-left' | 'fade-in-right'
525
- | 'zoom-in' | 'zoom-out' | 'zoom-in-up' | 'zoom-in-left' | 'zoom-in-right'
526
- | 'flip' | 'flip-x' | 'slide-rotate' | 'bounce-in';
527
-
528
- interface RuneScrollerOptions {
529
- animation?: AnimationType;
530
- duration?: number;
531
- repeat?: boolean;
532
- debug?: boolean;
533
- offset?: number;
534
- onVisible?: (element: HTMLElement) => void; // v2.0.0+
535
- sentinelColor?: string; // v2.0.0+
536
- sentinelId?: string; // v2.0.0+
537
- }
281
+ import type { AnimationType, RuneScrollerOptions } from "rune-scroller";
538
282
  ```
539
283
 
540
284
  ---
@@ -545,48 +289,49 @@ interface RuneScrollerOptions {
545
289
 
546
290
  ```svelte
547
291
  <script>
548
- import runeScroller from 'rune-scroller';
549
-
550
- const items = [
551
- { title: 'Feature 1', description: 'Description 1' },
552
- { title: 'Feature 2', description: 'Description 2' },
553
- { title: 'Feature 3', description: 'Description 3' }
554
- ];
292
+ import rs from 'rune-scroller';
293
+ const items = ['Item 1', 'Item 2', 'Item 3'];
555
294
  </script>
556
295
 
557
- <div class="grid">
558
- {#each items as item}
559
- <div use:runeScroller={{ animation: 'fade-in-up', duration: 800 }}>
560
- <h3>{item.title}</h3>
561
- <p>{item.description}</p>
562
- </div>
563
- {/each}
564
- </div>
296
+ {#each items as item, i}
297
+ <div use:rs={{ animation: 'fade-up', duration: 800, delay: i * 100 }}>
298
+ {item}
299
+ </div>
300
+ {/each}
565
301
  ```
566
302
 
567
303
  ### Hero Section
568
304
 
569
- ```svelte
570
- <div use:runeScroller={{ animation: 'fade-in-down', duration: 1000 }}>
571
- <h1>Welcome</h1>
572
- </div>
305
+ ```html
306
+ <h1 data-aos="fade-down" data-aos-duration="1000">Welcome</h1>
307
+ <p data-aos="fade-up" data-aos-duration="1200">Subtitle</p>
308
+ <button data-aos="zoom-in" data-aos-duration="800">Get Started</button>
309
+ ```
573
310
 
574
- <div use:runeScroller={{ animation: 'fade-in-up', duration: 1200 }}>
575
- <p>Engaging content</p>
576
- </div>
311
+ ---
577
312
 
578
- <div use:runeScroller={{ animation: 'zoom-in', duration: 1000 }}>
579
- <button>Get Started</button>
580
- </div>
313
+ ## 🔄 Replacing AOS
314
+
315
+ ```bash
316
+ npm uninstall aos
317
+ npm install rune-scroller
581
318
  ```
582
319
 
320
+ ```diff
321
+ - import AOS from 'aos';
322
+ - import 'aos/dist/aos.css';
323
+ + import AOS from 'rune-scroller/aos';
324
+ ```
325
+
326
+ Everything else stays the same. Same attributes, same options.
327
+
583
328
  ---
584
329
 
585
330
  ## 🔗 Links
586
331
 
587
- - **npm Package**: [rune-scroller](https://www.npmjs.com/package/rune-scroller)
332
+ - **npm**: [rune-scroller](https://www.npmjs.com/package/rune-scroller)
588
333
  - **GitHub**: [lelabdev/rune-scroller](https://github.com/lelabdev/rune-scroller)
589
- - **Changelog**: [CHANGELOG.md](https://github.com/lelabdev/rune-scroller/blob/main/lib/CHANGELOG.md)
334
+ - **Changelog**: [CHANGELOG.md](./CHANGELOG.md)
590
335
 
591
336
  ---
592
337
 
@@ -596,18 +341,4 @@ MIT © [ludoloops](https://github.com/ludoloops)
596
341
 
597
342
  ---
598
343
 
599
- ## 🤝 Contributing
600
-
601
- Contributions welcome! Please open an issue or PR on GitHub.
602
-
603
- ```bash
604
- # Development
605
- bun install
606
- bun run dev
607
- bun test
608
- bun run build
609
- ```
610
-
611
- ---
612
-
613
344
  Made with ❤️ by [LeLab.dev](https://lelab.dev)