rune-scroller 0.0.1 → 0.1.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 +492 -149
- package/dist/BaseAnimated.svelte +1 -6
- package/dist/{ScrollAnimate.svelte → Rs.svelte} +12 -5
- package/dist/Rs.svelte.d.ts +16 -0
- package/dist/animate.svelte.js +1 -5
- package/dist/animations.d.ts +6 -24
- package/dist/animations.js +2 -90
- package/dist/animations.test.d.ts +1 -0
- package/dist/animations.test.js +43 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +3 -3
- package/dist/runeScroller.svelte.d.ts +29 -0
- package/dist/runeScroller.svelte.js +76 -0
- package/dist/scroll-animate.test.d.ts +1 -0
- package/dist/scroll-animate.test.js +57 -0
- package/package.json +74 -77
- package/dist/AnimatedElements.svelte +0 -26
- package/dist/AnimatedElements.svelte.d.ts +0 -11
- package/dist/ScrollAnimate.svelte.d.ts +0 -13
- package/dist/assets/favicon.svg +0 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
- ✅ **Svelte 5 Runes** : `$state`, `$props()` with snippets
|
|
16
16
|
- ✅ **Zero Dependencies** : Pure Svelte 5 + IntersectionObserver
|
|
17
17
|
- ✅ **Native Performance** : GPU-accelerated CSS animations
|
|
18
|
-
- ✅ **
|
|
18
|
+
- ✅ **14 Built-in Animations** : Fade (5), Zoom (5), Flip (2), Slide Rotate, Bounce
|
|
19
19
|
- ✅ **TypeScript** : Full type coverage with strict mode
|
|
20
20
|
- ✅ **Customizable** : Duration, delay, threshold, offset per element
|
|
21
21
|
- ✅ **Play Once or Repeat** : Control animation behavior
|
|
@@ -55,73 +55,225 @@ For a typical SvelteKit app:
|
|
|
55
55
|
## 📦 Project Structure
|
|
56
56
|
|
|
57
57
|
```
|
|
58
|
-
rune-scroller/
|
|
58
|
+
rune-scroller-lib/
|
|
59
59
|
├── src/lib/
|
|
60
|
-
│ ├──
|
|
61
|
-
│ ├──
|
|
62
|
-
│ ├──
|
|
63
|
-
│ ├──
|
|
64
|
-
│ ├──
|
|
65
|
-
│
|
|
66
|
-
├──
|
|
67
|
-
│ ├──
|
|
68
|
-
│
|
|
69
|
-
└──
|
|
60
|
+
│ ├── Rs.svelte # Main animation component (one-time or repeat)
|
|
61
|
+
│ ├── BaseAnimated.svelte # Base animation implementation
|
|
62
|
+
│ ├── runeScroller.svelte.ts # Sentinel-based animation action (recommended)
|
|
63
|
+
│ ├── useIntersection.svelte.ts # IntersectionObserver composables
|
|
64
|
+
│ ├── animate.svelte.ts # Animation action for direct DOM control
|
|
65
|
+
│ ├── animations.ts # Animation configuration & validation
|
|
66
|
+
│ ├── animations.css # Animation styles (14 animations)
|
|
67
|
+
│ ├── animations.test.ts # Animation configuration tests
|
|
68
|
+
│ ├── scroll-animate.test.ts # Component behavior tests
|
|
69
|
+
│ └── index.ts # Library entry point
|
|
70
|
+
├── dist/ # Built library (created by pnpm build)
|
|
71
|
+
├── package.json # npm package configuration
|
|
72
|
+
├── svelte.config.js # SvelteKit configuration
|
|
73
|
+
├── vite.config.ts # Vite build configuration
|
|
74
|
+
├── tsconfig.json # TypeScript configuration
|
|
75
|
+
└── eslint.config.js # ESLint configuration
|
|
70
76
|
```
|
|
71
77
|
|
|
72
78
|
---
|
|
73
79
|
|
|
74
80
|
## 🚀 Quick Start
|
|
75
81
|
|
|
76
|
-
###
|
|
82
|
+
### Installation
|
|
77
83
|
|
|
78
|
-
|
|
84
|
+
```bash
|
|
85
|
+
npm install rune-scroller
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Or with other package managers:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pnpm add rune-scroller
|
|
92
|
+
yarn add rune-scroller
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Basic Usage with `Rs` Component
|
|
96
|
+
|
|
97
|
+
The `Rs` component is the main component for scroll animations. Use the `repeat` prop to control animation behavior:
|
|
79
98
|
|
|
80
99
|
```svelte
|
|
81
100
|
<script>
|
|
82
|
-
import
|
|
101
|
+
import Rs from 'rune-scroller';
|
|
102
|
+
import 'rune-scroller/animations.css';
|
|
83
103
|
</script>
|
|
84
104
|
|
|
85
|
-
|
|
86
|
-
|
|
105
|
+
<!-- Play animation once when element enters viewport (default) -->
|
|
106
|
+
<Rs animation="fade-in">
|
|
107
|
+
<div class="card">
|
|
87
108
|
<h2>Hello World</h2>
|
|
88
109
|
<p>This element fades in once</p>
|
|
89
110
|
</div>
|
|
90
|
-
</
|
|
91
|
-
```
|
|
111
|
+
</Rs>
|
|
92
112
|
|
|
93
|
-
|
|
113
|
+
<!-- Repeat animation every time element enters viewport -->
|
|
114
|
+
<Rs animation="zoom-in" repeat>
|
|
115
|
+
<div class="card">
|
|
116
|
+
<h2>Repeating Animation</h2>
|
|
117
|
+
<p>This triggers each time you scroll past it</p>
|
|
118
|
+
</div>
|
|
119
|
+
</Rs>
|
|
120
|
+
```
|
|
94
121
|
|
|
95
|
-
|
|
122
|
+
### In SvelteKit/Local Development
|
|
96
123
|
|
|
97
124
|
```svelte
|
|
98
125
|
<script>
|
|
99
|
-
import
|
|
126
|
+
import Rs from '$lib/Rs.svelte';
|
|
127
|
+
import '$lib/animations.css';
|
|
100
128
|
</script>
|
|
101
129
|
|
|
102
|
-
<
|
|
103
|
-
<div class="
|
|
104
|
-
<h2>
|
|
105
|
-
<p>This triggers each time you scroll past it</p>
|
|
130
|
+
<Rs animation="fade-in-up" duration={1000} delay={200}>
|
|
131
|
+
<div class="card">
|
|
132
|
+
<h2>Animated Content</h2>
|
|
106
133
|
</div>
|
|
107
|
-
</
|
|
134
|
+
</Rs>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### API Overview
|
|
138
|
+
|
|
139
|
+
The `Rs` component is the unified API for all scroll animations:
|
|
140
|
+
|
|
141
|
+
```svelte
|
|
142
|
+
<!-- Animation plays once (default) -->
|
|
143
|
+
<Rs animation="fade-in">Content</Rs>
|
|
144
|
+
|
|
145
|
+
<!-- Animation repeats on every scroll -->
|
|
146
|
+
<Rs animation="fade-in" repeat>Content</Rs>
|
|
147
|
+
|
|
148
|
+
<!-- Customize timing -->
|
|
149
|
+
<Rs animation="zoom-in" duration={1200} delay={300}>
|
|
150
|
+
Content
|
|
151
|
+
</Rs>
|
|
152
|
+
|
|
153
|
+
<!-- Use with any HTML attribute -->
|
|
154
|
+
<Rs animation="bounce-in" class="my-class" data-test="value">
|
|
155
|
+
Content
|
|
156
|
+
</Rs>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## 🎯 Sentinel-Based Animation Triggering with `runeScroller`
|
|
162
|
+
|
|
163
|
+
For more precise control over animation timing, use the `runeScroller` action. This approach uses an invisible **sentinel element** positioned below your content to trigger animations at exactly the right moment.
|
|
164
|
+
|
|
165
|
+
### Why Sentinels?
|
|
166
|
+
|
|
167
|
+
- **Accurate Timing** - Instead of triggering when the element enters, sentinel triggers slightly earlier
|
|
168
|
+
- **Consistent Behavior** - Same timing across all screen sizes and content heights
|
|
169
|
+
- **Simple API** - No complex offset calculations needed
|
|
170
|
+
- **Performance** - Minimal overhead, pure CSS animations
|
|
171
|
+
|
|
172
|
+
### How Sentinels Work
|
|
173
|
+
|
|
174
|
+
1. An invisible 20px sentinel element is automatically placed **below** your animated element
|
|
175
|
+
2. When the sentinel enters the viewport, it triggers the animation
|
|
176
|
+
3. This ensures content animates in perfectly as it becomes visible
|
|
177
|
+
|
|
178
|
+
```svelte
|
|
179
|
+
<div use:runeScroller={{ animation: 'fade-in-up', duration: 1000 }}>
|
|
180
|
+
<!-- Your content here -->
|
|
181
|
+
<!-- Invisible sentinel is automatically placed below -->
|
|
182
|
+
</div>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Basic Usage
|
|
186
|
+
|
|
187
|
+
```svelte
|
|
188
|
+
<script>
|
|
189
|
+
import { runeScroller } from 'rune-scroller';
|
|
190
|
+
import 'rune-scroller/animations.css';
|
|
191
|
+
</script>
|
|
192
|
+
|
|
193
|
+
<!-- Simple fade in with sentinel triggering -->
|
|
194
|
+
<div use:runeScroller={{ animation: 'fade-in' }}>
|
|
195
|
+
<h2>Animated Heading</h2>
|
|
196
|
+
<p>Animates when sentinel enters viewport</p>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
<!-- With duration control -->
|
|
200
|
+
<div use:runeScroller={{ animation: 'fade-in-up', duration: 1500 }}>
|
|
201
|
+
<div class="card">Smooth animation</div>
|
|
202
|
+
</div>
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Sentinel-Based Examples
|
|
206
|
+
|
|
207
|
+
**Staggered animations with sentinels:**
|
|
208
|
+
|
|
209
|
+
```svelte
|
|
210
|
+
<script>
|
|
211
|
+
import { runeScroller } from 'rune-scroller';
|
|
212
|
+
</script>
|
|
213
|
+
|
|
214
|
+
<div class="grid">
|
|
215
|
+
{#each items as item, i}
|
|
216
|
+
<div use:runeScroller={{ animation: 'fade-in-up', duration: 800 }}>
|
|
217
|
+
<h3>{item.title}</h3>
|
|
218
|
+
<p>{item.description}</p>
|
|
219
|
+
</div>
|
|
220
|
+
{/each}
|
|
221
|
+
</div>
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Hero section with sentinel triggering:**
|
|
225
|
+
|
|
226
|
+
```svelte
|
|
227
|
+
<div use:runeScroller={{ animation: 'fade-in-down', duration: 1000 }}>
|
|
228
|
+
<h1>Welcome to Our Site</h1>
|
|
229
|
+
</div>
|
|
230
|
+
|
|
231
|
+
<div use:runeScroller={{ animation: 'fade-in-up', duration: 1200 }}>
|
|
232
|
+
<p>Engaging content appears as you scroll</p>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
<div use:runeScroller={{ animation: 'zoom-in', duration: 1000 }}>
|
|
236
|
+
<button class="cta">Get Started</button>
|
|
237
|
+
</div>
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### `runeScroller` Options
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
interface RuneScrollerOptions {
|
|
244
|
+
animation?: AnimationType; // Animation type (e.g., 'fade-in-up')
|
|
245
|
+
duration?: number; // Duration in milliseconds (default: 2000)
|
|
246
|
+
repeat?: boolean; // Repeat animation on each scroll (default: false)
|
|
247
|
+
}
|
|
108
248
|
```
|
|
109
249
|
|
|
250
|
+
### Comparing: `Rs` Component vs `runeScroller` Action
|
|
251
|
+
|
|
252
|
+
| Feature | `Rs` Component | `runeScroller` Action |
|
|
253
|
+
|---------|---|---|
|
|
254
|
+
| **Usage** | `<Rs>` wrapper | `use:` directive |
|
|
255
|
+
| **Triggering** | IntersectionObserver on element | IntersectionObserver on sentinel |
|
|
256
|
+
| **Timing Control** | offset, threshold props | Automatic sentinel placement |
|
|
257
|
+
| **Repeat Support** | Yes (via `repeat` prop) | Yes (via `repeat` option) |
|
|
258
|
+
| **Best For** | Complex layouts, component isolation | Direct DOM control, simple/mixed elements |
|
|
259
|
+
|
|
110
260
|
---
|
|
111
261
|
|
|
112
262
|
## ⚙️ Component Props
|
|
113
263
|
|
|
114
|
-
###
|
|
264
|
+
### Rs Component
|
|
115
265
|
|
|
116
266
|
```typescript
|
|
117
|
-
interface
|
|
267
|
+
interface RsProps {
|
|
118
268
|
animation?: string; // Animation type (default: 'fade-in')
|
|
119
269
|
threshold?: number; // Visibility threshold (default: 0.5)
|
|
120
270
|
offset?: number; // Trigger offset 0-100% (optional, uses default if not set)
|
|
121
271
|
rootMargin?: string; // Observer margin (overrides offset if set)
|
|
122
272
|
duration?: number; // Duration in ms (default: 800)
|
|
123
273
|
delay?: number; // Delay in ms (default: 0)
|
|
274
|
+
repeat?: boolean; // Repeat animation on every scroll (default: false)
|
|
124
275
|
children: Snippet; // Content to animate
|
|
276
|
+
[key: string]: any; // Accepts any HTML attributes (e.g., data-testid, class, etc.)
|
|
125
277
|
}
|
|
126
278
|
```
|
|
127
279
|
|
|
@@ -138,45 +290,51 @@ Controls when the animation triggers as the element scrolls into view. If not sp
|
|
|
138
290
|
|
|
139
291
|
```svelte
|
|
140
292
|
<!-- Early trigger (bottom of screen) -->
|
|
141
|
-
<
|
|
142
|
-
<div class="
|
|
143
|
-
</
|
|
293
|
+
<Rs animation="fade-in-up" offset={0}>
|
|
294
|
+
<div class="card">Animates early</div>
|
|
295
|
+
</Rs>
|
|
144
296
|
|
|
145
297
|
<!-- Late trigger (top of screen) -->
|
|
146
|
-
<
|
|
147
|
-
<div class="
|
|
148
|
-
</
|
|
298
|
+
<Rs animation="fade-in-up" offset={100}>
|
|
299
|
+
<div class="card">Animates late</div>
|
|
300
|
+
</Rs>
|
|
149
301
|
|
|
150
302
|
<!-- Custom timing -->
|
|
151
|
-
<
|
|
152
|
-
<div class="
|
|
153
|
-
</
|
|
303
|
+
<Rs animation="fade-in-up" offset={75}>
|
|
304
|
+
<div class="card">Animates at 75%</div>
|
|
305
|
+
</Rs>
|
|
306
|
+
|
|
307
|
+
<!-- Repeat animation on every scroll -->
|
|
308
|
+
<Rs animation="fade-in-up" offset={50} repeat>
|
|
309
|
+
<div class="card">Animates every time</div>
|
|
310
|
+
</Rs>
|
|
154
311
|
```
|
|
155
312
|
|
|
156
313
|
**Full example with all props:**
|
|
157
314
|
|
|
158
315
|
```svelte
|
|
159
|
-
<
|
|
160
|
-
|
|
316
|
+
<Rs
|
|
317
|
+
animation="fade-in-up"
|
|
318
|
+
duration={1200}
|
|
319
|
+
delay={300}
|
|
320
|
+
threshold={0.8}
|
|
321
|
+
offset={25}
|
|
322
|
+
repeat={false}
|
|
323
|
+
data-testid="custom-animation"
|
|
324
|
+
>
|
|
325
|
+
<div class="card">
|
|
161
326
|
<h2>Custom Timing</h2>
|
|
162
327
|
<p>Duration: 1200ms, Delay: 300ms, Threshold: 80%, Offset: 25%</p>
|
|
163
328
|
</div>
|
|
164
|
-
</
|
|
329
|
+
</Rs>
|
|
165
330
|
```
|
|
166
331
|
|
|
167
|
-
|
|
332
|
+
#### Repeat Behavior
|
|
168
333
|
|
|
169
|
-
|
|
170
|
-
interface AnimatedElementsProps {
|
|
171
|
-
animation?: string; // Animation type (default: 'fade-in')
|
|
172
|
-
threshold?: number; // Visibility threshold (default: 0.5)
|
|
173
|
-
offset?: number; // Trigger offset 0-100% (optional, uses default if not set)
|
|
174
|
-
rootMargin?: string; // Observer margin (overrides offset if set)
|
|
175
|
-
children: Snippet; // Content to animate
|
|
176
|
-
}
|
|
177
|
-
```
|
|
334
|
+
Use the `repeat` prop to control animation behavior:
|
|
178
335
|
|
|
179
|
-
|
|
336
|
+
- `repeat={false}` (default) - Animation plays **once** when element enters viewport
|
|
337
|
+
- `repeat={true}` - Animation **repeats** every time element enters viewport
|
|
180
338
|
|
|
181
339
|
---
|
|
182
340
|
|
|
@@ -189,12 +347,12 @@ Same `offset` behavior as `ScrollAnimate`, but animation **repeats on every scro
|
|
|
189
347
|
Simple opacity fade from transparent to visible.
|
|
190
348
|
|
|
191
349
|
```svelte
|
|
192
|
-
<
|
|
193
|
-
<div class="
|
|
350
|
+
<Rs animation="fade-in">
|
|
351
|
+
<div class="card">
|
|
194
352
|
<h2>Fade In</h2>
|
|
195
353
|
<p>Simple fade entrance</p>
|
|
196
354
|
</div>
|
|
197
|
-
</
|
|
355
|
+
</Rs>
|
|
198
356
|
```
|
|
199
357
|
|
|
200
358
|
#### `fade-in-up`
|
|
@@ -202,12 +360,12 @@ Simple opacity fade from transparent to visible.
|
|
|
202
360
|
Fades in while moving up 100px.
|
|
203
361
|
|
|
204
362
|
```svelte
|
|
205
|
-
<
|
|
206
|
-
<div class="
|
|
363
|
+
<Rs animation="fade-in-up">
|
|
364
|
+
<div class="card">
|
|
207
365
|
<h2>Fade In Up</h2>
|
|
208
366
|
<p>Rises from below</p>
|
|
209
367
|
</div>
|
|
210
|
-
</
|
|
368
|
+
</Rs>
|
|
211
369
|
```
|
|
212
370
|
|
|
213
371
|
#### `fade-in-down`
|
|
@@ -215,12 +373,12 @@ Fades in while moving up 100px.
|
|
|
215
373
|
Fades in while moving down 100px.
|
|
216
374
|
|
|
217
375
|
```svelte
|
|
218
|
-
<
|
|
219
|
-
<div class="
|
|
376
|
+
<Rs animation="fade-in-down">
|
|
377
|
+
<div class="card">
|
|
220
378
|
<h2>Fade In Down</h2>
|
|
221
379
|
<p>Descends from above</p>
|
|
222
380
|
</div>
|
|
223
|
-
</
|
|
381
|
+
</Rs>
|
|
224
382
|
```
|
|
225
383
|
|
|
226
384
|
#### `fade-in-left`
|
|
@@ -228,12 +386,12 @@ Fades in while moving down 100px.
|
|
|
228
386
|
Fades in while moving left 100px.
|
|
229
387
|
|
|
230
388
|
```svelte
|
|
231
|
-
<
|
|
232
|
-
<div class="
|
|
389
|
+
<Rs animation="fade-in-left">
|
|
390
|
+
<div class="card">
|
|
233
391
|
<h2>Fade In Left</h2>
|
|
234
392
|
<p>Comes from the right</p>
|
|
235
393
|
</div>
|
|
236
|
-
</
|
|
394
|
+
</Rs>
|
|
237
395
|
```
|
|
238
396
|
|
|
239
397
|
#### `fade-in-right`
|
|
@@ -241,29 +399,29 @@ Fades in while moving left 100px.
|
|
|
241
399
|
Fades in while moving right 100px.
|
|
242
400
|
|
|
243
401
|
```svelte
|
|
244
|
-
<
|
|
245
|
-
<div class="
|
|
402
|
+
<Rs animation="fade-in-right">
|
|
403
|
+
<div class="card">
|
|
246
404
|
<h2>Fade In Right</h2>
|
|
247
405
|
<p>Comes from the left</p>
|
|
248
406
|
</div>
|
|
249
|
-
</
|
|
407
|
+
</Rs>
|
|
250
408
|
```
|
|
251
409
|
|
|
252
410
|
---
|
|
253
411
|
|
|
254
|
-
### Zoom (
|
|
412
|
+
### Zoom (5 variants)
|
|
255
413
|
|
|
256
414
|
#### `zoom-in`
|
|
257
415
|
|
|
258
416
|
Scales from 50% to 100% while fading in.
|
|
259
417
|
|
|
260
418
|
```svelte
|
|
261
|
-
<
|
|
262
|
-
<div class="
|
|
419
|
+
<Rs animation="zoom-in">
|
|
420
|
+
<div class="card">
|
|
263
421
|
<h2>Zoom In</h2>
|
|
264
422
|
<p>Grows into view</p>
|
|
265
423
|
</div>
|
|
266
|
-
</
|
|
424
|
+
</Rs>
|
|
267
425
|
```
|
|
268
426
|
|
|
269
427
|
#### `zoom-out`
|
|
@@ -271,12 +429,51 @@ Scales from 50% to 100% while fading in.
|
|
|
271
429
|
Scales from 150% to 100% while fading in.
|
|
272
430
|
|
|
273
431
|
```svelte
|
|
274
|
-
<
|
|
275
|
-
<div class="
|
|
432
|
+
<Rs animation="zoom-out">
|
|
433
|
+
<div class="card">
|
|
276
434
|
<h2>Zoom Out</h2>
|
|
277
435
|
<p>Shrinks into view</p>
|
|
278
436
|
</div>
|
|
279
|
-
</
|
|
437
|
+
</Rs>
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
#### `zoom-in-up`
|
|
441
|
+
|
|
442
|
+
Scales from 50% while translating up 50px.
|
|
443
|
+
|
|
444
|
+
```svelte
|
|
445
|
+
<Rs animation="zoom-in-up">
|
|
446
|
+
<div class="card">
|
|
447
|
+
<h2>Zoom In Up</h2>
|
|
448
|
+
<p>Grows while moving up</p>
|
|
449
|
+
</div>
|
|
450
|
+
</Rs>
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
#### `zoom-in-left`
|
|
454
|
+
|
|
455
|
+
Scales from 50% while translating left 50px.
|
|
456
|
+
|
|
457
|
+
```svelte
|
|
458
|
+
<Rs animation="zoom-in-left">
|
|
459
|
+
<div class="card">
|
|
460
|
+
<h2>Zoom In Left</h2>
|
|
461
|
+
<p>Grows while moving left</p>
|
|
462
|
+
</div>
|
|
463
|
+
</Rs>
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
#### `zoom-in-right`
|
|
467
|
+
|
|
468
|
+
Scales from 50% while translating right 50px.
|
|
469
|
+
|
|
470
|
+
```svelte
|
|
471
|
+
<Rs animation="zoom-in-right">
|
|
472
|
+
<div class="card">
|
|
473
|
+
<h2>Zoom In Right</h2>
|
|
474
|
+
<p>Grows while moving right</p>
|
|
475
|
+
</div>
|
|
476
|
+
</Rs>
|
|
280
477
|
```
|
|
281
478
|
|
|
282
479
|
---
|
|
@@ -288,12 +485,12 @@ Scales from 150% to 100% while fading in.
|
|
|
288
485
|
3D rotation on Y axis (left to right).
|
|
289
486
|
|
|
290
487
|
```svelte
|
|
291
|
-
<
|
|
292
|
-
<div class="
|
|
488
|
+
<Rs animation="flip">
|
|
489
|
+
<div class="card">
|
|
293
490
|
<h2>Flip</h2>
|
|
294
491
|
<p>Rotates on Y axis</p>
|
|
295
492
|
</div>
|
|
296
|
-
</
|
|
493
|
+
</Rs>
|
|
297
494
|
```
|
|
298
495
|
|
|
299
496
|
#### `flip-x`
|
|
@@ -301,12 +498,12 @@ Scales from 150% to 100% while fading in.
|
|
|
301
498
|
3D rotation on X axis (top to bottom).
|
|
302
499
|
|
|
303
500
|
```svelte
|
|
304
|
-
<
|
|
305
|
-
<div class="
|
|
501
|
+
<Rs animation="flip-x">
|
|
502
|
+
<div class="card">
|
|
306
503
|
<h2>Flip X</h2>
|
|
307
504
|
<p>Rotates on X axis</p>
|
|
308
505
|
</div>
|
|
309
|
-
</
|
|
506
|
+
</Rs>
|
|
310
507
|
```
|
|
311
508
|
|
|
312
509
|
---
|
|
@@ -318,12 +515,12 @@ Scales from 150% to 100% while fading in.
|
|
|
318
515
|
Slides from left while rotating 45 degrees.
|
|
319
516
|
|
|
320
517
|
```svelte
|
|
321
|
-
<
|
|
322
|
-
<div class="
|
|
518
|
+
<Rs animation="slide-rotate">
|
|
519
|
+
<div class="card">
|
|
323
520
|
<h2>Slide Rotate</h2>
|
|
324
521
|
<p>Slides and spins</p>
|
|
325
522
|
</div>
|
|
326
|
-
</
|
|
523
|
+
</Rs>
|
|
327
524
|
```
|
|
328
525
|
|
|
329
526
|
---
|
|
@@ -335,30 +532,30 @@ Slides from left while rotating 45 degrees.
|
|
|
335
532
|
Bouncy entrance with scaling keyframe animation.
|
|
336
533
|
|
|
337
534
|
```svelte
|
|
338
|
-
<
|
|
339
|
-
<div class="
|
|
535
|
+
<Rs animation="bounce-in" duration={800}>
|
|
536
|
+
<div class="card">
|
|
340
537
|
<h2>Bounce In</h2>
|
|
341
538
|
<p>Bounces into view</p>
|
|
342
539
|
</div>
|
|
343
|
-
</
|
|
540
|
+
</Rs>
|
|
344
541
|
```
|
|
345
542
|
|
|
346
543
|
---
|
|
347
544
|
|
|
348
545
|
### Compare: Once vs Repeat
|
|
349
546
|
|
|
350
|
-
**Same animation, different behavior:**
|
|
547
|
+
**Same animation, different behavior using the `repeat` prop:**
|
|
351
548
|
|
|
352
549
|
```svelte
|
|
353
|
-
<!-- Plays once on scroll down -->
|
|
354
|
-
<
|
|
355
|
-
<div class="
|
|
356
|
-
</
|
|
550
|
+
<!-- Plays once on scroll down (default) -->
|
|
551
|
+
<Rs animation="fade-in-up">
|
|
552
|
+
<div class="card">Animates once</div>
|
|
553
|
+
</Rs>
|
|
357
554
|
|
|
358
555
|
<!-- Repeats each time you scroll by -->
|
|
359
|
-
<
|
|
360
|
-
<div class="
|
|
361
|
-
</
|
|
556
|
+
<Rs animation="fade-in-up" repeat>
|
|
557
|
+
<div class="card">Animates on every scroll</div>
|
|
558
|
+
</Rs>
|
|
362
559
|
```
|
|
363
560
|
|
|
364
561
|
---
|
|
@@ -371,17 +568,17 @@ Animate cards with progressive delays:
|
|
|
371
568
|
|
|
372
569
|
```svelte
|
|
373
570
|
<script>
|
|
374
|
-
import
|
|
571
|
+
import Rs from '$lib/Rs.svelte';
|
|
375
572
|
</script>
|
|
376
573
|
|
|
377
574
|
<div class="grid">
|
|
378
575
|
{#each items as item, i}
|
|
379
|
-
<
|
|
380
|
-
<div class="
|
|
576
|
+
<Rs animation="fade-in-up" delay={i * 100}>
|
|
577
|
+
<div class="card">
|
|
381
578
|
<h3>{item.title}</h3>
|
|
382
579
|
<p>{item.description}</p>
|
|
383
580
|
</div>
|
|
384
|
-
</
|
|
581
|
+
</Rs>
|
|
385
582
|
{/each}
|
|
386
583
|
</div>
|
|
387
584
|
```
|
|
@@ -389,100 +586,154 @@ Animate cards with progressive delays:
|
|
|
389
586
|
### Mixed Animations
|
|
390
587
|
|
|
391
588
|
```svelte
|
|
392
|
-
<
|
|
589
|
+
<Rs animation="fade-in">
|
|
393
590
|
<section>Content fades in</section>
|
|
394
|
-
</
|
|
591
|
+
</Rs>
|
|
395
592
|
|
|
396
|
-
<
|
|
593
|
+
<Rs animation="slide-rotate">
|
|
397
594
|
<section>Content slides and rotates</section>
|
|
398
|
-
</
|
|
595
|
+
</Rs>
|
|
399
596
|
|
|
400
|
-
<
|
|
597
|
+
<Rs animation="zoom-in" repeat>
|
|
401
598
|
<section>Content zooms in repeatedly</section>
|
|
402
|
-
</
|
|
599
|
+
</Rs>
|
|
403
600
|
```
|
|
404
601
|
|
|
405
602
|
### Hero Section
|
|
406
603
|
|
|
407
604
|
```svelte
|
|
408
605
|
<script>
|
|
409
|
-
import
|
|
606
|
+
import Rs from '$lib/Rs.svelte';
|
|
410
607
|
</script>
|
|
411
608
|
|
|
412
609
|
<section class="hero">
|
|
413
610
|
<div class="hero-content">
|
|
414
|
-
<
|
|
611
|
+
<Rs animation="fade-in" delay={0}>
|
|
415
612
|
<h1>Welcome</h1>
|
|
416
|
-
</
|
|
613
|
+
</Rs>
|
|
417
614
|
|
|
418
|
-
<
|
|
615
|
+
<Rs animation="fade-in" delay={200}>
|
|
419
616
|
<p>Scroll to reveal more</p>
|
|
420
|
-
</
|
|
617
|
+
</Rs>
|
|
421
618
|
|
|
422
|
-
<
|
|
619
|
+
<Rs animation="zoom-in" delay={400}>
|
|
423
620
|
<button>Get Started</button>
|
|
424
|
-
</
|
|
621
|
+
</Rs>
|
|
425
622
|
</div>
|
|
426
623
|
</section>
|
|
427
624
|
```
|
|
428
625
|
|
|
429
626
|
---
|
|
430
627
|
|
|
431
|
-
##
|
|
628
|
+
## 🔧 Composables & Actions
|
|
432
629
|
|
|
433
|
-
|
|
630
|
+
### runeScroller (Recommended)
|
|
434
631
|
|
|
435
|
-
|
|
632
|
+
The `runeScroller` action provides sentinel-based animation triggering for precise timing control:
|
|
436
633
|
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
634
|
+
```typescript
|
|
635
|
+
function runeScroller(
|
|
636
|
+
element: HTMLElement,
|
|
637
|
+
options?: {
|
|
638
|
+
animation?: AnimationType; // Animation type
|
|
639
|
+
duration?: number; // Duration in ms (default: 2000)
|
|
640
|
+
repeat?: boolean; // Repeat animation on each scroll (default: false)
|
|
641
|
+
}
|
|
642
|
+
): { update?: (newOptions) => void; destroy?: () => void }
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
**Key Features:**
|
|
646
|
+
- Automatically creates an invisible 20px sentinel element below your content
|
|
647
|
+
- Triggers animation when sentinel enters viewport
|
|
648
|
+
- Provides consistent timing across all screen sizes
|
|
649
|
+
- Minimal configuration needed
|
|
650
|
+
- Supports both one-time and repeating animations
|
|
651
|
+
|
|
652
|
+
**Basic Example (One-time animation):**
|
|
653
|
+
|
|
654
|
+
```svelte
|
|
655
|
+
<script>
|
|
656
|
+
import { runeScroller } from 'rune-scroller';
|
|
657
|
+
</script>
|
|
658
|
+
|
|
659
|
+
<!-- Animation plays once when sentinel enters viewport -->
|
|
660
|
+
<div use:runeScroller={{ animation: 'fade-in-up', duration: 1000 }}>
|
|
661
|
+
Animated content with sentinel-based triggering
|
|
662
|
+
</div>
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
**Repeating Animation:**
|
|
666
|
+
|
|
667
|
+
```svelte
|
|
668
|
+
<!-- Animation repeats each time sentinel enters viewport -->
|
|
669
|
+
<div use:runeScroller={{ animation: 'bounce-in', duration: 800, repeat: true }}>
|
|
670
|
+
This animates every time you scroll past it
|
|
671
|
+
</div>
|
|
444
672
|
```
|
|
445
673
|
|
|
446
|
-
|
|
674
|
+
**Complete Examples:**
|
|
447
675
|
|
|
448
676
|
```svelte
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
677
|
+
<script>
|
|
678
|
+
import { runeScroller } from 'rune-scroller';
|
|
679
|
+
</script>
|
|
680
|
+
|
|
681
|
+
<!-- Fade in once on scroll -->
|
|
682
|
+
<div use:runeScroller={{ animation: 'fade-in', duration: 600 }}>
|
|
683
|
+
<h2>Section Title</h2>
|
|
684
|
+
<p>Fades in when scrolled into view</p>
|
|
453
685
|
</div>
|
|
454
686
|
|
|
455
|
-
<!--
|
|
456
|
-
<div
|
|
457
|
-
<
|
|
458
|
-
|
|
687
|
+
<!-- Zoom in with longer duration -->
|
|
688
|
+
<div use:runeScroller={{ animation: 'zoom-in-up', duration: 1200 }}>
|
|
689
|
+
<div class="card">
|
|
690
|
+
<h3>Card Title</h3>
|
|
691
|
+
<p>Zooms in from below</p>
|
|
692
|
+
</div>
|
|
459
693
|
</div>
|
|
460
694
|
|
|
461
|
-
<!--
|
|
462
|
-
<div
|
|
695
|
+
<!-- Repeating animation for interactive effect -->
|
|
696
|
+
<div use:runeScroller={{ animation: 'bounce-in', duration: 700, repeat: true }}>
|
|
697
|
+
<button class="interactive-button">Bounces on each scroll</button>
|
|
698
|
+
</div>
|
|
699
|
+
|
|
700
|
+
<!-- Complex staggered layout -->
|
|
701
|
+
<div class="grid">
|
|
702
|
+
{#each items as item, i}
|
|
703
|
+
<div use:runeScroller={{ animation: 'fade-in-up', duration: 800 }}>
|
|
704
|
+
<h3>{item.title}</h3>
|
|
705
|
+
<p>{item.description}</p>
|
|
706
|
+
</div>
|
|
707
|
+
{/each}
|
|
708
|
+
</div>
|
|
463
709
|
```
|
|
464
710
|
|
|
465
|
-
|
|
711
|
+
**When to use:**
|
|
712
|
+
- ✅ Simple element animations
|
|
713
|
+
- ✅ Consistent timing across layouts
|
|
714
|
+
- ✅ Minimal overhead applications
|
|
715
|
+
- ✅ Both one-time and repeating animations
|
|
716
|
+
- ❌ Complex layout with component isolation (use `Rs` component instead)
|
|
466
717
|
|
|
467
|
-
|
|
718
|
+
---
|
|
468
719
|
|
|
469
720
|
### useIntersectionOnce
|
|
470
721
|
|
|
471
|
-
For animations
|
|
722
|
+
For one-time animations:
|
|
472
723
|
|
|
473
724
|
```typescript
|
|
474
725
|
function useIntersectionOnce(options?: {
|
|
475
726
|
threshold?: number;
|
|
476
727
|
rootMargin?: string;
|
|
477
728
|
root?: Element | null;
|
|
478
|
-
});
|
|
729
|
+
}): { element: HTMLElement | null; isVisible: boolean }
|
|
479
730
|
```
|
|
480
731
|
|
|
481
|
-
Returns `{ element, isVisible }` — bind `element` to your target, `isVisible` becomes `true` once.
|
|
732
|
+
Returns `{ element, isVisible }` — bind `element` to your target, `isVisible` becomes `true` once, then observer unobserves.
|
|
482
733
|
|
|
483
734
|
### useIntersection
|
|
484
735
|
|
|
485
|
-
For repeating animations
|
|
736
|
+
For repeating animations:
|
|
486
737
|
|
|
487
738
|
```typescript
|
|
488
739
|
function useIntersection(
|
|
@@ -492,22 +743,62 @@ function useIntersection(
|
|
|
492
743
|
root?: Element | null;
|
|
493
744
|
},
|
|
494
745
|
onVisible?: (isVisible: boolean) => void
|
|
495
|
-
);
|
|
746
|
+
): { element: HTMLElement | null; isVisible: boolean }
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
Returns `{ element, isVisible }` — `isVisible` toggles `true`/`false` on each scroll pass.
|
|
750
|
+
|
|
751
|
+
### animate Action
|
|
752
|
+
|
|
753
|
+
For direct DOM animation control without component wrapper:
|
|
754
|
+
|
|
755
|
+
```typescript
|
|
756
|
+
function animate(
|
|
757
|
+
node: HTMLElement,
|
|
758
|
+
options?: {
|
|
759
|
+
animation?: AnimationType; // Default: 'fade-in'
|
|
760
|
+
duration?: number; // Default: 800
|
|
761
|
+
delay?: number; // Default: 0
|
|
762
|
+
offset?: number; // Optional trigger offset
|
|
763
|
+
threshold?: number; // Default: 0
|
|
764
|
+
rootMargin?: string; // Optional custom margin
|
|
765
|
+
}
|
|
766
|
+
): { update?: (newOptions) => void; destroy?: () => void }
|
|
496
767
|
```
|
|
497
768
|
|
|
498
|
-
|
|
769
|
+
**Example:**
|
|
770
|
+
|
|
771
|
+
```svelte
|
|
772
|
+
<script>
|
|
773
|
+
import { animate } from 'rune-scroller';
|
|
774
|
+
</script>
|
|
775
|
+
|
|
776
|
+
<div use:animate={{ animation: 'fade-in-up', duration: 1000, delay: 200 }}>
|
|
777
|
+
Animated content
|
|
778
|
+
</div>
|
|
779
|
+
```
|
|
499
780
|
|
|
500
781
|
---
|
|
501
782
|
|
|
502
783
|
## 🏗️ Architecture
|
|
503
784
|
|
|
504
|
-
###
|
|
785
|
+
### Core Layer Architecture
|
|
786
|
+
|
|
787
|
+
**Bottom Layer - Browser APIs & Utilities:**
|
|
788
|
+
1. **animations.ts** - Animation type definitions, validation, and utilities
|
|
789
|
+
2. **dom-utils.svelte.ts** - Reusable DOM manipulation utilities (CSS variables, animation setup, sentinel creation)
|
|
790
|
+
3. **useIntersection.svelte.ts** - IntersectionObserver composables for element visibility detection
|
|
791
|
+
|
|
792
|
+
**Middle Layer - Base Implementation:**
|
|
793
|
+
4. **animate.svelte.ts** - Action for direct DOM node animation control
|
|
794
|
+
5. **runeScroller.svelte.ts** - **Recommended** - Sentinel-based action for precise animation timing
|
|
795
|
+
6. **BaseAnimated.svelte** - Base component handling intersection observer + animation logic
|
|
796
|
+
|
|
797
|
+
**Top Layer - Consumer API:**
|
|
798
|
+
7. **Rs.svelte** - Main unified component (supports one-time & repeating via `repeat` prop)
|
|
505
799
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
3. **useIntersection.svelte.ts** - IntersectionObserver logic
|
|
509
|
-
4. **ScrollAnimate.svelte** - One-time wrapper component
|
|
510
|
-
5. **AnimatedElements.svelte** - Repeating wrapper component
|
|
800
|
+
**Styles:**
|
|
801
|
+
- **animations.css** - All animation keyframes & styles (14 animations, GPU-accelerated)
|
|
511
802
|
|
|
512
803
|
### Key Principles
|
|
513
804
|
|
|
@@ -515,6 +806,45 @@ Returns `{ element, isVisible }` — `isVisible` toggles on each scroll.
|
|
|
515
806
|
- **CSS-Based** : Animations use CSS transforms + transitions (hardware-accelerated)
|
|
516
807
|
- **Type-Safe** : Full TypeScript support
|
|
517
808
|
- **Composable** : Use hooks directly or wrapped components
|
|
809
|
+
- **DRY (Don't Repeat Yourself)** : Utility functions eliminate code duplication
|
|
810
|
+
- **Optimal DOM Manipulation** : Uses `cssText` for efficient single-statement styling
|
|
811
|
+
|
|
812
|
+
---
|
|
813
|
+
|
|
814
|
+
## 🚀 Optimizations
|
|
815
|
+
|
|
816
|
+
### Recent Improvements (v1.1.0)
|
|
817
|
+
|
|
818
|
+
**DOM Utility Extraction**
|
|
819
|
+
- Extracted repeated DOM manipulation patterns into reusable utilities (`dom-utils.svelte.ts`)
|
|
820
|
+
- `setCSSVariables()` - Centralizes CSS custom property management
|
|
821
|
+
- `setupAnimationElement()` - Consistent animation class/attribute setup
|
|
822
|
+
- `createSentinel()` - Optimized sentinel creation using single `cssText` statement
|
|
823
|
+
- **Result**: Reduced code duplication, improved maintainability, cleaner codebase
|
|
824
|
+
|
|
825
|
+
**Memory Leak Fixes**
|
|
826
|
+
- Fixed potential memory leaks in repeat mode by tracking observer connection state
|
|
827
|
+
- Observer now properly disconnects in destroy lifecycle
|
|
828
|
+
- Prevents accumulation of observers on long-scroll pages
|
|
829
|
+
- **Result**: Better performance on content-heavy sites with many animations
|
|
830
|
+
|
|
831
|
+
**Observer Logic Improvements**
|
|
832
|
+
- Fixed `animate.svelte.ts` to properly handle dynamic threshold/rootMargin changes
|
|
833
|
+
- Observer now recreates when trigger options change at runtime
|
|
834
|
+
- Maintains correct state throughout component lifecycle
|
|
835
|
+
- **Result**: More reliable dynamic animation updates
|
|
836
|
+
|
|
837
|
+
**Bundle Size Optimization**
|
|
838
|
+
- Updated `.npmignore` to exclude test files from npm distribution
|
|
839
|
+
- Removes `*.test.ts`, `*.test.js` and built test files
|
|
840
|
+
- **Result**: ~3.6 KB reduction in package size
|
|
841
|
+
|
|
842
|
+
### Performance Impact
|
|
843
|
+
|
|
844
|
+
- **Code Size**: Reduced duplication without sacrificing readability
|
|
845
|
+
- **Runtime Performance**: Fewer DOM operations via optimized `cssText` usage
|
|
846
|
+
- **Memory Efficiency**: Proper observer cleanup prevents memory leaks
|
|
847
|
+
- **Bundle Size**: Test files excluded from distribution
|
|
518
848
|
|
|
519
849
|
---
|
|
520
850
|
|
|
@@ -540,10 +870,22 @@ pnpm dev
|
|
|
540
870
|
# Type checking
|
|
541
871
|
pnpm check
|
|
542
872
|
|
|
873
|
+
# Type checking in watch mode
|
|
874
|
+
pnpm check:watch
|
|
875
|
+
|
|
543
876
|
# Format code
|
|
544
877
|
pnpm format
|
|
545
878
|
|
|
546
|
-
#
|
|
879
|
+
# Lint code
|
|
880
|
+
pnpm lint
|
|
881
|
+
|
|
882
|
+
# Build library for npm
|
|
883
|
+
pnpm build
|
|
884
|
+
|
|
885
|
+
# Run tests
|
|
886
|
+
pnpm test
|
|
887
|
+
|
|
888
|
+
# Preview built library
|
|
547
889
|
pnpm preview
|
|
548
890
|
```
|
|
549
891
|
|
|
@@ -552,9 +894,10 @@ pnpm preview
|
|
|
552
894
|
## 📝 Notes
|
|
553
895
|
|
|
554
896
|
- **Why "Rune"?** Svelte 5 uses **Runes** (`$state`, `$props()`) as core reactivity primitives
|
|
555
|
-
- **
|
|
556
|
-
- **No Dependencies** : Pure Svelte 5 + Browser APIs
|
|
897
|
+
- **Zero Dependencies** : Pure Svelte 5 + Native Browser APIs (IntersectionObserver)
|
|
557
898
|
- **Extensible** : Add new animations by extending `animations.ts` and `animations.css`
|
|
899
|
+
- **Library Only** : This is the library repository. The demo website is in `rune-scroller-site`
|
|
900
|
+
- **Published as npm Package** : `rune-scroller` on npm registry
|
|
558
901
|
|
|
559
902
|
---
|
|
560
903
|
|