rune-scroller 0.1.9 → 0.1.11

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.
Files changed (2) hide show
  1. package/README.md +196 -581
  2. package/package.json +14 -15
package/README.md CHANGED
@@ -4,109 +4,48 @@
4
4
  <img src="./logo.png" alt="Rune Scroller Logo" width="200" />
5
5
  </div>
6
6
 
7
- **Native Scroll Animations for Svelte 5** — Built with **Svelte 5 Runes** and **IntersectionObserver API**. No external dependencies, pure performance.
7
+ **Lightweight scroll animations for Svelte 5** — Built with Svelte 5 Runes and IntersectionObserver API.
8
8
 
9
- > 🚀 **Open Source Project** by **[ludoloops](https://github.com/ludoloops)** at **[LeLab.dev](https://lelab.dev)**
10
- > 📜 Licensed under **MIT** — Contributions welcome!
11
- >
12
- > A modern, lightweight scroll animation library showcasing Svelte 5 capabilities
9
+ > 🚀 **Open Source** by [ludoloops](https://github.com/ludoloops) at [LeLab.dev](https://lelab.dev)
10
+ > 📜 Licensed under **MIT**
13
11
 
14
12
  ---
15
13
 
16
14
  ## ✨ Features
17
15
 
18
- - **~2KB Bundle** : Only **1.9 KB gzipped** (52% lighter than AOS!)
19
- - **Svelte 5 Runes** : `$state`, `$props()` with snippets
20
- - **Zero Dependencies** : Pure Svelte 5 + IntersectionObserver
21
- - **Native Performance** : GPU-accelerated CSS animations
22
- - **14 Built-in Animations** : Fade (5), Zoom (5), Flip (2), Slide Rotate, Bounce
23
- - **TypeScript** : Full type coverage with strict mode
24
- - **Customizable** : Duration, delay, threshold, offset per element
25
- - ✅ **Play Once or Repeat** : Control animation behavior
26
- - ✅ **SSR-ready** : SvelteKit compatible with no DOM access during hydration
27
- - ✅ **Accessible** : Respects `prefers-reduced-motion` media query
16
+ - **~2KB gzipped** - Minimal overhead
17
+ - **Zero dependencies** - Pure Svelte 5 + IntersectionObserver
18
+ - **14 animations** - Fade, Zoom, Flip, Slide, Bounce variants
19
+ - **TypeScript** - Full type coverage
20
+ - **SSR-ready** - SvelteKit compatible
21
+ - **GPU-accelerated** - Pure CSS transforms
22
+ - **Accessible** - Respects `prefers-reduced-motion`
28
23
 
29
24
  ---
30
25
 
31
- ## Performance: Svelte Projects Bundle Comparison
32
-
33
- ### When Using in a Svelte Project
34
-
35
- | Scenario | Bundle Size | Impact |
36
- | ------------------------- | ----------------- | -------------------- |
37
- | **Svelte App (baseline)** | ~30-35 KB gzipped | - |
38
- | **+ AOS Library** | ~34-39 KB | **+4 KB overhead** |
39
- | **+ Rune Scroller** | ~31.9-36.9 KB | **+1.9 KB overhead** |
40
- | **Savings** | **2.1 KB** | **52% smaller** ✨ |
41
-
42
- ### Why Rune Scroller is Lighter
43
-
44
- 1. **Native Svelte Integration** - Uses `$state()` directly (no separate state lib)
45
- 2. **CSS-Based Animations** - Pure CSS transforms + GPU acceleration (no JS animation loop)
46
- 3. **Svelte 5 Optimized** - Leverages runes system for minimal overhead
47
- 4. **Zero External Dependencies** - Works with Svelte's native IntersectionObserver
48
-
49
- ### Real-World Impact
50
-
51
- For a typical SvelteKit app:
52
-
53
- - **With AOS**: Extra 4 KB per user download
54
- - **With Rune Scroller**: Extra 1.9 KB per user download
55
- - **Difference**: Save **2.1 KB per page load** = faster initial paint! 🚀
56
-
57
- ---
58
-
59
- ## 📦 Project Structure
60
-
61
- ```
62
- rune-scroller-lib/
63
- ├── src/lib/
64
- │ ├── runeScroller.svelte.ts # Main animation action (sentinel-based)
65
- │ ├── useIntersection.svelte.ts # IntersectionObserver composables
66
- │ ├── animate.svelte.ts # Animation action for direct DOM control
67
- │ ├── animations.ts # Animation configuration & validation
68
- │ ├── animations.css # Animation styles (14 animations)
69
- │ ├── animations.test.ts # Animation configuration tests
70
- │ ├── scroll-animate.test.ts # Component behavior tests
71
- │ └── index.ts # Library entry point
72
- ├── dist/ # Built library (created by pnpm build)
73
- ├── package.json # npm package configuration
74
- ├── svelte.config.js # SvelteKit configuration
75
- ├── vite.config.ts # Vite build configuration
76
- ├── tsconfig.json # TypeScript configuration
77
- └── eslint.config.js # ESLint configuration
78
- ```
79
-
80
- ---
81
-
82
- ## 🚀 Quick Start
83
-
84
- ### Installation
26
+ ## 📦 Installation
85
27
 
86
28
  ```bash
87
29
  npm install rune-scroller
88
- ```
89
-
90
- Or with other package managers:
91
-
92
- ```bash
30
+ # or
93
31
  pnpm add rune-scroller
32
+ # or
94
33
  yarn add rune-scroller
95
34
  ```
96
35
 
97
- ### Basic Usage with `runeScroller` Action
36
+ ---
98
37
 
99
- Use the `runeScroller` action with the `use:` directive for sentinel-based animation triggering:
38
+ ## 🚀 Quick Start
100
39
 
101
40
  ```svelte
102
41
  <script>
103
42
  import runeScroller from 'rune-scroller';
43
+ import 'rune-scroller/animations.css';
104
44
  </script>
105
45
 
106
- <!-- Simple fade in animation -->
46
+ <!-- Simple animation -->
107
47
  <div use:runeScroller={{ animation: 'fade-in' }}>
108
48
  <h2>Animated Heading</h2>
109
- <p>Animates when scrolled into view</p>
110
49
  </div>
111
50
 
112
51
  <!-- With custom duration -->
@@ -114,422 +53,256 @@ Use the `runeScroller` action with the `use:` directive for sentinel-based anima
114
53
  <div class="card">Smooth fade and slide</div>
115
54
  </div>
116
55
 
117
- <!-- Repeat animation on every scroll -->
118
- <div use:runeScroller={{ animation: 'bounce-in', duration: 800, repeat: true }}>
56
+ <!-- Repeat on every scroll -->
57
+ <div use:runeScroller={{ animation: 'bounce-in', repeat: true }}>
119
58
  <button>Bounces on every scroll</button>
120
59
  </div>
121
60
  ```
122
61
 
123
- ### How It Works
124
-
125
- The `runeScroller` action uses an invisible **sentinel element** for precise animation triggering:
126
-
127
- 1. **Sentinel Creation** - An invisible sentinel is created and positioned **absolutely** at your element's bottom
128
- 2. **Parent Context** - Parent element automatically gets `position: relative` if needed (no visual impact)
129
- 3. **Viewport Detection** - When the sentinel enters the viewport, it triggers the animation
130
- 4. **Fixed Positioning** - The sentinel stays fixed while your element animates, ensuring accurate timing
131
- 5. **Invisible by Default** - The sentinel is 1px tall and invisible (`visibility:hidden`)
132
- 6. **Debug Mode** - Add `debug: true` to see the sentinel as a visible cyan line (useful for development)
133
-
134
62
  ---
135
63
 
136
- ## 🎯 Sentinel-Based Animation Triggering
137
-
138
- ### Why Sentinels?
139
-
140
- - **Accurate Timing** - Triggers at the perfect moment for smooth animations
141
- - **Consistent Behavior** - Same timing across all screen sizes and content heights
142
- - **Simple API** - No complex offset calculations needed
143
- - **Performance** - Minimal overhead, pure CSS animations
144
- - **Reliable** - Automatic sentinel placement handles edge cases
145
-
146
- ### Sentinel-Based Examples
147
-
148
- **Staggered animations with sentinels:**
149
-
150
- ```svelte
151
- <script>
152
- import runeScroller from 'rune-scroller';
153
- </script>
64
+ ## 🎨 Available Animations
154
65
 
155
- <div class="grid">
156
- {#each items as item, i}
157
- <div use:runeScroller={{ animation: 'fade-in-up', duration: 800 }}>
158
- <h3>{item.title}</h3>
159
- <p>{item.description}</p>
160
- </div>
161
- {/each}
162
- </div>
163
- ```
66
+ ### Fade (5)
67
+ - `fade-in` - Simple opacity fade
68
+ - `fade-in-up` - Fade + move up 100px
69
+ - `fade-in-down` - Fade + move down 100px
70
+ - `fade-in-left` - Fade + move from right
71
+ - `fade-in-right` - Fade + move from left
164
72
 
165
- **Hero section with sentinel triggering:**
73
+ ### Zoom (5)
74
+ - `zoom-in` - Scale from 0.6 to 1
75
+ - `zoom-out` - Scale from 1.2 to 1
76
+ - `zoom-in-up` - Zoom + move up
77
+ - `zoom-in-left` - Zoom + move from right
78
+ - `zoom-in-right` - Zoom + move from left
166
79
 
167
- ```svelte
168
- <div use:runeScroller={{ animation: 'fade-in-down', duration: 1000 }}>
169
- <h1>Welcome to Our Site</h1>
170
- </div>
80
+ ### Others (4)
81
+ - `flip` - 3D flip on Y-axis
82
+ - `flip-x` - 3D flip on X-axis
83
+ - `slide-rotate` - Slide + rotate 10°
84
+ - `bounce-in` - Bouncy entrance (spring effect)
171
85
 
172
- <div use:runeScroller={{ animation: 'fade-in-up', duration: 1200 }}>
173
- <p>Engaging content appears as you scroll</p>
174
- </div>
175
-
176
- <div use:runeScroller={{ animation: 'zoom-in', duration: 1000 }}>
177
- <button class="cta">Get Started</button>
178
- </div>
179
- ```
86
+ ---
180
87
 
181
- ### `runeScroller` Options
88
+ ## ⚙️ Options
182
89
 
183
90
  ```typescript
184
91
  interface RuneScrollerOptions {
185
- animation?: AnimationType; // Animation type (e.g., 'fade-in-up')
186
- duration?: number; // Duration in milliseconds (default: 2000)
187
- repeat?: boolean; // Repeat animation on each scroll (default: false)
92
+ animation?: AnimationType; // Animation name (default: 'fade-in')
93
+ duration?: number; // Duration in ms (default: 2000)
94
+ repeat?: boolean; // Repeat on scroll (default: false)
95
+ debug?: boolean; // Show sentinel element (default: false)
188
96
  }
189
97
  ```
190
98
 
191
- ---
192
-
193
- ## 🎨 All Animations with Examples
194
-
195
- ### Fade (5 variants)
196
-
197
- #### `fade-in`
198
-
199
- Simple opacity fade from transparent to visible.
200
-
201
- ```svelte
202
- <script>
203
- import runeScroller from 'rune-scroller';
204
- </script>
205
-
206
- <div use:runeScroller={{ animation: 'fade-in' }}>
207
- <h2>Fade In</h2>
208
- <p>Simple fade entrance</p>
209
- </div>
210
- ```
211
-
212
- #### `fade-in-up`
213
-
214
- Fades in while moving up 100px.
215
-
216
- ```svelte
217
- <div use:runeScroller={{ animation: 'fade-in-up' }}>
218
- <h2>Fade In Up</h2>
219
- <p>Rises from below</p>
220
- </div>
221
- ```
222
-
223
- #### `fade-in-down`
224
-
225
- Fades in while moving down 100px.
226
-
227
- ```svelte
228
- <div use:runeScroller={{ animation: 'fade-in-down' }}>
229
- <h2>Fade In Down</h2>
230
- <p>Descends from above</p>
231
- </div>
232
- ```
233
-
234
- #### `fade-in-left`
235
-
236
- Fades in while moving left 100px.
237
-
238
- ```svelte
239
- <div use:runeScroller={{ animation: 'fade-in-left' }}>
240
- <h2>Fade In Left</h2>
241
- <p>Comes from the right</p>
242
- </div>
243
- ```
244
-
245
- #### `fade-in-right`
246
-
247
- Fades in while moving right 100px.
248
-
249
- ```svelte
250
- <div use:runeScroller={{ animation: 'fade-in-right' }}>
251
- <h2>Fade In Right</h2>
252
- <p>Comes from the left</p>
253
- </div>
254
- ```
255
-
256
- ---
257
-
258
- ### Zoom (5 variants)
259
-
260
- #### `zoom-in`
261
-
262
- Scales from 50% to 100% while fading in.
99
+ ### Examples
263
100
 
264
101
  ```svelte
102
+ <!-- Basic -->
265
103
  <div use:runeScroller={{ animation: 'zoom-in' }}>
266
- <h2>Zoom In</h2>
267
- <p>Grows into view</p>
104
+ Content
268
105
  </div>
269
- ```
270
-
271
- #### `zoom-out`
272
-
273
- Scales from 150% to 100% while fading in.
274
106
 
275
- ```svelte
276
- <div use:runeScroller={{ animation: 'zoom-out' }}>
277
- <h2>Zoom Out</h2>
278
- <p>Shrinks into view</p>
107
+ <!-- Custom duration -->
108
+ <div use:runeScroller={{ animation: 'fade-in-up', duration: 1000 }}>
109
+ Fast animation
279
110
  </div>
280
- ```
281
-
282
- #### `zoom-in-up`
283
111
 
284
- Scales from 50% while translating up 50px.
285
-
286
- ```svelte
287
- <div use:runeScroller={{ animation: 'zoom-in-up' }}>
288
- <h2>Zoom In Up</h2>
289
- <p>Grows while moving up</p>
112
+ <!-- Repeat mode -->
113
+ <div use:runeScroller={{ animation: 'bounce-in', repeat: true }}>
114
+ Repeats every time you scroll
290
115
  </div>
291
- ```
292
-
293
- #### `zoom-in-left`
294
116
 
295
- Scales from 50% while translating left 50px.
296
-
297
- ```svelte
298
- <div use:runeScroller={{ animation: 'zoom-in-left' }}>
299
- <h2>Zoom In Left</h2>
300
- <p>Grows while moving left</p>
301
- </div>
302
- ```
303
-
304
- #### `zoom-in-right`
305
-
306
- Scales from 50% while translating right 50px.
307
-
308
- ```svelte
309
- <div use:runeScroller={{ animation: 'zoom-in-right' }}>
310
- <h2>Zoom In Right</h2>
311
- <p>Grows while moving right</p>
117
+ <!-- Debug mode (shows invisible sentinel) -->
118
+ <div use:runeScroller={{ animation: 'fade-in', debug: true }}>
119
+ You'll see a cyan line (the sentinel trigger)
312
120
  </div>
313
121
  ```
314
122
 
315
123
  ---
316
124
 
317
- ### Flip (2 variants)
125
+ ## 🔧 Advanced Usage
318
126
 
319
- #### `flip`
127
+ ### Using the `animate` Action (Direct Control)
320
128
 
321
- 3D rotation on Y axis (left to right).
129
+ For advanced use cases, use `animate` for fine-grained IntersectionObserver control:
322
130
 
323
131
  ```svelte
324
- <div use:runeScroller={{ animation: 'flip' }}>
325
- <h2>Flip</h2>
326
- <p>Rotates on Y axis</p>
327
- </div>
328
- ```
329
-
330
- #### `flip-x`
331
-
332
- 3D rotation on X axis (top to bottom).
132
+ <script>
133
+ import { animate } from 'rune-scroller';
134
+ import 'rune-scroller/animations.css';
135
+ </script>
333
136
 
334
- ```svelte
335
- <div use:runeScroller={{ animation: 'flip-x' }}>
336
- <h2>Flip X</h2>
337
- <p>Rotates on X axis</p>
137
+ <div use:animate={{
138
+ animation: 'fade-in-up',
139
+ duration: 1000,
140
+ delay: 200,
141
+ threshold: 0.5,
142
+ offset: 20,
143
+ once: true
144
+ }}>
145
+ Advanced control
338
146
  </div>
339
147
  ```
340
148
 
341
- ---
342
-
343
- ### Slide & Rotate
149
+ **Options:**
150
+ - `threshold` - Intersection ratio to trigger (0-1)
151
+ - `offset` - Viewport offset percentage (0-100)
152
+ - `rootMargin` - Custom IntersectionObserver margin
153
+ - `delay` - Animation delay in ms
154
+ - `once` - Trigger only once
344
155
 
345
- #### `slide-rotate`
346
-
347
- Slides from left while rotating 45 degrees.
156
+ ### Using Composables
348
157
 
349
158
  ```svelte
350
- <div use:runeScroller={{ animation: 'slide-rotate' }}>
351
- <h2>Slide Rotate</h2>
352
- <p>Slides and spins</p>
353
- </div>
354
- ```
355
-
356
- ---
357
-
358
- ### Bounce
359
-
360
- #### `bounce-in`
159
+ <script>
160
+ import { useIntersectionOnce } from 'rune-scroller';
161
+ import 'rune-scroller/animations.css';
361
162
 
362
- Bouncy entrance with scaling keyframe animation.
163
+ const intersection = useIntersectionOnce({ threshold: 0.5 });
164
+ </script>
363
165
 
364
- ```svelte
365
- <div use:runeScroller={{ animation: 'bounce-in', duration: 800 }}>
366
- <h2>Bounce In</h2>
367
- <p>Bounces into view</p>
166
+ <div
167
+ bind:this={intersection.element}
168
+ class="scroll-animate"
169
+ class:is-visible={intersection.isVisible}
170
+ data-animation="fade-in-up"
171
+ >
172
+ Manual control over intersection state
368
173
  </div>
369
174
  ```
370
175
 
371
176
  ---
372
177
 
373
- ### One-Time vs Repeat Animations
178
+ ## 🎯 How It Works
374
179
 
375
- Control animation behavior with the `repeat` option:
180
+ Rune Scroller uses **sentinel-based triggering**:
376
181
 
377
- ```svelte
378
- <!-- Plays once on scroll (default) -->
379
- <div use:runeScroller={{ animation: 'fade-in-up' }}>
380
- Animates once when scrolled into view
381
- </div>
182
+ 1. An invisible 1px sentinel element is created below your element
183
+ 2. When the sentinel enters the viewport, animation triggers
184
+ 3. This ensures precise timing regardless of element size
185
+ 4. Uses native IntersectionObserver for performance
186
+ 5. Pure CSS animations (GPU-accelerated)
382
187
 
383
- <!-- Repeats each time you scroll by -->
384
- <div use:runeScroller={{ animation: 'fade-in-up', repeat: true }}>
385
- Animates every time you scroll past it
386
- </div>
387
- ```
188
+ **Why sentinels?**
189
+ - Accurate timing across all screen sizes
190
+ - No complex offset calculations
191
+ - Handles staggered animations naturally
388
192
 
389
193
  ---
390
194
 
391
- ## 💡 Usage Examples
392
-
393
- ### Staggered Grid
195
+ ## 🌐 SSR Compatibility
394
196
 
395
- Animate cards with progressive delays:
197
+ Works seamlessly with SvelteKit:
396
198
 
397
199
  ```svelte
398
200
  <script>
399
201
  import runeScroller from 'rune-scroller';
202
+ import 'rune-scroller/animations.css';
400
203
  </script>
401
204
 
402
- <div class="grid">
403
- {#each items as item, i}
404
- <div use:runeScroller={{ animation: 'fade-in-up', duration: 800 + i * 100 }}>
405
- <div class="card">
406
- <h3>{item.title}</h3>
407
- <p>{item.description}</p>
408
- </div>
409
- </div>
410
- {/each}
205
+ <!-- No special handling needed -->
206
+ <div use:runeScroller={{ animation: 'fade-in-up' }}>
207
+ Works in SvelteKit SSR!
411
208
  </div>
412
209
  ```
413
210
 
414
- ### Mixed Animations
211
+ The library checks for browser environment and gracefully handles server-side rendering.
415
212
 
416
- ```svelte
417
- <script>
418
- import runeScroller from 'rune-scroller';
419
- </script>
213
+ ---
420
214
 
421
- <section use:runeScroller={{ animation: 'fade-in' }}>
422
- Content fades in
423
- </section>
215
+ ## Accessibility
424
216
 
425
- <section use:runeScroller={{ animation: 'slide-rotate' }}>
426
- Content slides and rotates
427
- </section>
217
+ Respects `prefers-reduced-motion`:
428
218
 
429
- <section use:runeScroller={{ animation: 'zoom-in', repeat: true }}>
430
- Content zooms in repeatedly
431
- </section>
219
+ ```css
220
+ /* In animations.css */
221
+ @media (prefers-reduced-motion: reduce) {
222
+ .scroll-animate {
223
+ animation: none !important;
224
+ opacity: 1 !important;
225
+ transform: none !important;
226
+ }
227
+ }
432
228
  ```
433
229
 
434
- ### Hero Section
435
-
436
- ```svelte
437
- <script>
438
- import runeScroller from 'rune-scroller';
439
- </script>
440
-
441
- <section class="hero">
442
- <h1 use:runeScroller={{ animation: 'fade-in', duration: 1000 }}>
443
- Welcome
444
- </h1>
445
-
446
- <p use:runeScroller={{ animation: 'fade-in', duration: 1200 }}>
447
- Scroll to reveal more
448
- </p>
449
-
450
- <button use:runeScroller={{ animation: 'zoom-in', duration: 1000 }}>
451
- Get Started
452
- </button>
453
- </section>
454
- ```
230
+ Users who prefer reduced motion will see content without animations.
455
231
 
456
232
  ---
457
233
 
458
- ## 🔧 Composables & Actions
459
-
460
- ### runeScroller (Recommended)
234
+ ## 📚 API Reference
461
235
 
462
- The `runeScroller` action provides sentinel-based animation triggering for precise timing control:
236
+ ### Main Export
463
237
 
464
238
  ```typescript
465
- function runeScroller(
466
- element: HTMLElement,
467
- options?: {
468
- animation?: AnimationType; // Animation type
469
- duration?: number; // Duration in ms (default: 2000)
470
- repeat?: boolean; // Repeat animation on each scroll (default: false)
471
- }
472
- ): { update?: (newOptions) => void; destroy?: () => void }
239
+ // Default export (recommended)
240
+ import runeScroller from 'rune-scroller';
241
+
242
+ // Named exports
243
+ import {
244
+ animate, // Alternative action
245
+ useIntersection, // Composable
246
+ useIntersectionOnce, // Composable
247
+ calculateRootMargin // Utility
248
+ } from 'rune-scroller';
249
+
250
+ // Types
251
+ import type {
252
+ AnimationType,
253
+ RuneScrollerOptions,
254
+ AnimateOptions,
255
+ IntersectionOptions,
256
+ UseIntersectionReturn
257
+ } from 'rune-scroller';
473
258
  ```
474
259
 
475
- **Key Features:**
476
- - Automatically creates an invisible 20px sentinel element below your content
477
- - Triggers animation when sentinel enters viewport
478
- - Provides consistent timing across all screen sizes
479
- - Minimal configuration needed
480
- - Supports both one-time and repeating animations
260
+ ### TypeScript Types
481
261
 
482
- **Basic Example (One-time animation):**
262
+ ```typescript
263
+ type AnimationType =
264
+ | 'fade-in' | 'fade-in-up' | 'fade-in-down' | 'fade-in-left' | 'fade-in-right'
265
+ | 'zoom-in' | 'zoom-out' | 'zoom-in-up' | 'zoom-in-left' | 'zoom-in-right'
266
+ | 'flip' | 'flip-x' | 'slide-rotate' | 'bounce-in';
483
267
 
484
- ```svelte
485
- <script>
486
- import runeScroller from 'rune-scroller';
487
- </script>
268
+ interface RuneScrollerOptions {
269
+ animation?: AnimationType;
270
+ duration?: number;
271
+ repeat?: boolean;
272
+ debug?: boolean;
273
+ }
488
274
 
489
- <!-- Animation plays once when sentinel enters viewport -->
490
- <div use:runeScroller={{ animation: 'fade-in-up', duration: 1000 }}>
491
- Animated content with sentinel-based triggering
492
- </div>
275
+ interface AnimateOptions {
276
+ animation?: AnimationType;
277
+ duration?: number;
278
+ delay?: number;
279
+ threshold?: number;
280
+ rootMargin?: string;
281
+ offset?: number;
282
+ once?: boolean;
283
+ }
493
284
  ```
494
285
 
495
- **Repeating Animation:**
286
+ ---
496
287
 
497
- ```svelte
498
- <!-- Animation repeats each time sentinel enters viewport -->
499
- <div use:runeScroller={{ animation: 'bounce-in', duration: 800, repeat: true }}>
500
- This animates every time you scroll past it
501
- </div>
502
- ```
288
+ ## 📖 Examples
503
289
 
504
- **Complete Examples:**
290
+ ### Staggered Animations
505
291
 
506
292
  ```svelte
507
293
  <script>
508
294
  import runeScroller from 'rune-scroller';
509
- </script>
510
-
511
- <!-- Fade in once on scroll -->
512
- <div use:runeScroller={{ animation: 'fade-in', duration: 600 }}>
513
- <h2>Section Title</h2>
514
- <p>Fades in when scrolled into view</p>
515
- </div>
516
-
517
- <!-- Zoom in with longer duration -->
518
- <div use:runeScroller={{ animation: 'zoom-in-up', duration: 1200 }}>
519
- <div class="card">
520
- <h3>Card Title</h3>
521
- <p>Zooms in from below</p>
522
- </div>
523
- </div>
295
+ import 'rune-scroller/animations.css';
524
296
 
525
- <!-- Repeating animation for interactive effect -->
526
- <div use:runeScroller={{ animation: 'bounce-in', duration: 700, repeat: true }}>
527
- <button class="interactive-button">Bounces on each scroll</button>
528
- </div>
297
+ const items = [
298
+ { title: 'Feature 1', description: 'Description 1' },
299
+ { title: 'Feature 2', description: 'Description 2' },
300
+ { title: 'Feature 3', description: 'Description 3' }
301
+ ];
302
+ </script>
529
303
 
530
- <!-- Complex staggered layout -->
531
304
  <div class="grid">
532
- {#each items as item, i}
305
+ {#each items as item}
533
306
  <div use:runeScroller={{ animation: 'fade-in-up', duration: 800 }}>
534
307
  <h3>{item.title}</h3>
535
308
  <p>{item.description}</p>
@@ -538,209 +311,51 @@ function runeScroller(
538
311
  </div>
539
312
  ```
540
313
 
541
- **When to use:**
542
- - ✅ Simple element animations
543
- - ✅ Consistent timing across layouts
544
- - ✅ Minimal overhead applications
545
- - ✅ Both one-time and repeating animations
546
-
547
- ---
548
-
549
- ### useIntersectionOnce
550
-
551
- For one-time animations:
552
-
553
- ```typescript
554
- function useIntersectionOnce(options?: {
555
- threshold?: number;
556
- rootMargin?: string;
557
- root?: Element | null;
558
- }): { element: HTMLElement | null; isVisible: boolean }
559
- ```
560
-
561
- Returns `{ element, isVisible }` — bind `element` to your target, `isVisible` becomes `true` once, then observer unobserves.
562
-
563
- ### useIntersection
564
-
565
- For repeating animations:
566
-
567
- ```typescript
568
- function useIntersection(
569
- options?: {
570
- threshold?: number;
571
- rootMargin?: string;
572
- root?: Element | null;
573
- },
574
- onVisible?: (isVisible: boolean) => void
575
- ): { element: HTMLElement | null; isVisible: boolean }
576
- ```
577
-
578
- Returns `{ element, isVisible }` — `isVisible` toggles `true`/`false` on each scroll pass.
579
-
580
- ### animate Action
581
-
582
- For direct DOM animation control without component wrapper:
583
-
584
- ```typescript
585
- function animate(
586
- node: HTMLElement,
587
- options?: {
588
- animation?: AnimationType; // Default: 'fade-in'
589
- duration?: number; // Default: 800
590
- delay?: number; // Default: 0
591
- offset?: number; // Optional trigger offset
592
- threshold?: number; // Default: 0
593
- rootMargin?: string; // Optional custom margin
594
- }
595
- ): { update?: (newOptions) => void; destroy?: () => void }
596
- ```
597
-
598
- **Example:**
314
+ ### Hero Section
599
315
 
600
316
  ```svelte
601
- <script>
602
- import { animate } from 'rune-scroller';
603
- </script>
317
+ <div use:runeScroller={{ animation: 'fade-in-down', duration: 1000 }}>
318
+ <h1>Welcome</h1>
319
+ </div>
320
+
321
+ <div use:runeScroller={{ animation: 'fade-in-up', duration: 1200 }}>
322
+ <p>Engaging content</p>
323
+ </div>
604
324
 
605
- <div use:animate={{ animation: 'fade-in-up', duration: 1000, delay: 200 }}>
606
- Animated content
325
+ <div use:runeScroller={{ animation: 'zoom-in', duration: 1000 }}>
326
+ <button>Get Started</button>
607
327
  </div>
608
328
  ```
609
329
 
610
330
  ---
611
331
 
612
- ## 🏗️ Architecture
613
-
614
- ### Core Layer Architecture
615
-
616
- **Bottom Layer - Browser APIs & Utilities:**
617
- 1. **animations.ts** - Animation type definitions, validation, and utilities
618
- 2. **dom-utils.svelte.ts** - Reusable DOM manipulation utilities (CSS variables, animation setup, sentinel creation)
619
- 3. **useIntersection.svelte.ts** - IntersectionObserver composables for element visibility detection
620
-
621
- **Top Layer - Consumer API:**
622
- 4. **runeScroller.svelte.ts** - **Recommended** - Sentinel-based action for scroll animation triggering
623
- 5. **animate.svelte.ts** - Alternative action for direct DOM node animation control
624
-
625
- **Styles:**
626
- - **animations.css** - All animation keyframes & styles (14 animations, GPU-accelerated)
627
-
628
- ### Key Principles
332
+ ## 🔗 Links
629
333
 
630
- - **Separation of Concerns** : Scroll logic separate from components
631
- - **CSS-Based** : Animations use CSS transforms + transitions (hardware-accelerated)
632
- - **Type-Safe** : Full TypeScript support
633
- - **Composable** : Use hooks directly or wrapped components
634
- - **DRY (Don't Repeat Yourself)** : Utility functions eliminate code duplication
635
- - **Optimal DOM Manipulation** : Uses `cssText` for efficient single-statement styling
334
+ - **npm Package**: [rune-scroller](https://www.npmjs.com/package/rune-scroller)
335
+ - **GitHub**: [lelabdev/rune-scroller](https://github.com/lelabdev/rune-scroller)
336
+ - **Documentation**: [CLAUDE.md](./CLAUDE.md)
337
+ - **Changelog**: [CHANGELOG.md](./CHANGELOG.md)
636
338
 
637
339
  ---
638
340
 
639
- ## 🚀 Optimizations
640
-
641
- ### Recent Improvements (v1.1.0)
642
-
643
- **DOM Utility Extraction**
644
- - Extracted repeated DOM manipulation patterns into reusable utilities (`dom-utils.svelte.ts`)
645
- - `setCSSVariables()` - Centralizes CSS custom property management
646
- - `setupAnimationElement()` - Consistent animation class/attribute setup
647
- - `createSentinel()` - Optimized sentinel creation using single `cssText` statement
648
- - **Result**: Reduced code duplication, improved maintainability, cleaner codebase
649
-
650
- **Memory Leak Fixes**
651
- - Fixed potential memory leaks in repeat mode by tracking observer connection state
652
- - Observer now properly disconnects in destroy lifecycle
653
- - Prevents accumulation of observers on long-scroll pages
654
- - **Result**: Better performance on content-heavy sites with many animations
341
+ ## 📄 License
655
342
 
656
- **Observer Logic Improvements**
657
- - Fixed `animate.svelte.ts` to properly handle dynamic threshold/rootMargin changes
658
- - Observer now recreates when trigger options change at runtime
659
- - Maintains correct state throughout component lifecycle
660
- - **Result**: More reliable dynamic animation updates
661
-
662
- **Bundle Size Optimization**
663
- - Updated `.npmignore` to exclude test files from npm distribution
664
- - Removes `*.test.ts`, `*.test.js` and built test files
665
- - **Result**: ~3.6 KB reduction in package size
666
-
667
- ### Performance Impact
668
-
669
- - **Code Size**: Reduced duplication without sacrificing readability
670
- - **Runtime Performance**: Fewer DOM operations via optimized `cssText` usage
671
- - **Memory Efficiency**: Proper observer cleanup prevents memory leaks
672
- - **Bundle Size**: Test files excluded from distribution
343
+ MIT © [ludoloops](https://github.com/ludoloops)
673
344
 
674
345
  ---
675
346
 
676
- ## 📊 Performance
677
-
678
- - **IntersectionObserver** : Native browser API, no scroll listeners
679
- - **CSS Transforms** : Hardware-accelerated (GPU)
680
- - **Lazy Loading** : Only animate visible elements
681
- - **Memory Efficient** : Automatic cleanup on unmount
682
- - **SSR Compatible** : No DOM access during hydration
347
+ ## 🤝 Contributing
683
348
 
684
- ---
685
-
686
- ## 🎯 Development
349
+ Contributions welcome! Please open an issue or PR on GitHub.
687
350
 
688
351
  ```bash
689
- # Install dependencies
352
+ # Development
690
353
  pnpm install
691
-
692
- # Dev server
693
354
  pnpm dev
694
-
695
- # Type checking
696
- pnpm check
697
-
698
- # Type checking in watch mode
699
- pnpm check:watch
700
-
701
- # Format code
702
- pnpm format
703
-
704
- # Lint code
705
- pnpm lint
706
-
707
- # Build library for npm
708
- pnpm build
709
-
710
- # Run tests
711
355
  pnpm test
712
-
713
- # Preview built library
714
- pnpm preview
356
+ pnpm build
715
357
  ```
716
358
 
717
359
  ---
718
360
 
719
- ## 📝 Notes
720
-
721
- - **Why "Rune"?** Svelte 5 uses **Runes** (`$state`, `$props()`) as core reactivity primitives
722
- - **Zero Dependencies** : Pure Svelte 5 + Native Browser APIs (IntersectionObserver)
723
- - **Extensible** : Add new animations by extending `animations.ts` and `animations.css`
724
- - **Library Only** : This is the library repository. The demo website is in `rune-scroller-site`
725
- - **Published as npm Package** : `rune-scroller` on npm registry
726
-
727
- ---
728
-
729
- ## 🔗 Links
730
-
731
- - [Live Demo](https://runescroller.lelab.dev/) - Interactive showcase of all animations
732
- - [npm Package](https://www.npmjs.com/package/rune-scroller) - Install from npm
733
- - [Svelte 5 Documentation](https://svelte.dev)
734
- - [IntersectionObserver MDN](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)
735
- - [LeLab.dev](https://lelab.dev)
736
- - [GitHub Repository](https://github.com/ludoloops/rune-scroller)
737
-
738
- ---
739
-
740
- ## 📄 License & Credits
741
-
742
- **MIT License** — Free for personal and commercial use.
743
-
744
- Made with ⚡ by **[ludoloops](https://github.com/ludoloops)** at **[LeLab.dev](https://lelab.dev)**
745
-
746
- **Open Source Project** — Contributions, issues, and feature requests are welcome!
361
+ Made with ❤️ by [LeLab.dev](https://lelab.dev)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rune-scroller",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Lightweight, high-performance scroll animations for Svelte 5. ~2KB bundle, zero dependencies.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -40,19 +40,6 @@
40
40
  "peerDependencies": {
41
41
  "svelte": "^5.0.0"
42
42
  },
43
- "scripts": {
44
- "dev": "vite dev",
45
- "build": "svelte-package && node scripts/fix-dist.js",
46
- "preview": "vite preview",
47
- "prepare": "svelte-kit sync || echo ''",
48
- "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
49
- "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
50
- "format": "prettier --write .",
51
- "lint": "prettier --check . && eslint .",
52
- "prepublishonly": "pnpm run check && pnpm run build",
53
- "test:unit": "vitest",
54
- "test": "npm run test:unit -- --run"
55
- },
56
43
  "devDependencies": {
57
44
  "@eslint/compat": "^1.4.0",
58
45
  "@eslint/js": "^9.36.0",
@@ -72,5 +59,17 @@
72
59
  "typescript-eslint": "^8.44.1",
73
60
  "vite": "^7.1.7",
74
61
  "vitest": "^3.2.4"
62
+ },
63
+ "scripts": {
64
+ "dev": "vite dev",
65
+ "build": "svelte-package && node scripts/fix-dist.js",
66
+ "preview": "vite preview",
67
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
68
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
69
+ "format": "prettier --write .",
70
+ "lint": "prettier --check . && eslint .",
71
+ "prepublishonly": "pnpm run check && pnpm run build",
72
+ "test:unit": "vitest",
73
+ "test": "npm run test:unit -- --run"
75
74
  }
76
- }
75
+ }