rune-scroller 0.1.10 → 1.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 +78 -5
- package/dist/animations.css +10 -0
- package/dist/dom-utils.svelte.d.ts +4 -3
- package/dist/dom-utils.svelte.js +8 -6
- package/dist/index.d.ts +3 -3
- package/dist/runeScroller.svelte.js +16 -12
- package/dist/types.d.ts +1 -0
- package/package.json +15 -14
package/README.md
CHANGED
|
@@ -37,11 +37,37 @@ yarn add rune-scroller
|
|
|
37
37
|
|
|
38
38
|
## 🚀 Quick Start
|
|
39
39
|
|
|
40
|
+
### Step 1: Import CSS (required)
|
|
41
|
+
|
|
42
|
+
**⚠️ Important:** You must import the CSS file once in your app.
|
|
43
|
+
|
|
44
|
+
**Option A - In your root layout (recommended for SvelteKit):**
|
|
45
|
+
|
|
46
|
+
```svelte
|
|
47
|
+
<!-- src/routes/+layout.svelte -->
|
|
48
|
+
<script>
|
|
49
|
+
import 'rune-scroller/animations.css';
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<slot />
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Option B - In each component that uses animations:**
|
|
56
|
+
|
|
40
57
|
```svelte
|
|
41
58
|
<script>
|
|
42
59
|
import runeScroller from 'rune-scroller';
|
|
43
60
|
import 'rune-scroller/animations.css';
|
|
44
61
|
</script>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Step 2: Use the animations
|
|
65
|
+
|
|
66
|
+
```svelte
|
|
67
|
+
<script>
|
|
68
|
+
import runeScroller from 'rune-scroller';
|
|
69
|
+
// CSS already imported in layout or above
|
|
70
|
+
</script>
|
|
45
71
|
|
|
46
72
|
<!-- Simple animation -->
|
|
47
73
|
<div use:runeScroller={{ animation: 'fade-in' }}>
|
|
@@ -92,10 +118,19 @@ interface RuneScrollerOptions {
|
|
|
92
118
|
animation?: AnimationType; // Animation name (default: 'fade-in')
|
|
93
119
|
duration?: number; // Duration in ms (default: 2000)
|
|
94
120
|
repeat?: boolean; // Repeat on scroll (default: false)
|
|
95
|
-
debug?: boolean; // Show sentinel
|
|
121
|
+
debug?: boolean; // Show sentinel as visible line (default: false)
|
|
122
|
+
offset?: number; // Sentinel offset in px (default: 0, negative = above)
|
|
96
123
|
}
|
|
97
124
|
```
|
|
98
125
|
|
|
126
|
+
### Option Details
|
|
127
|
+
|
|
128
|
+
- **`animation`** - Type of animation to play. Choose from 14 built-in animations listed above.
|
|
129
|
+
- **`duration`** - How long the animation lasts in milliseconds (default: 2000ms).
|
|
130
|
+
- **`repeat`** - If `true`, animation plays every time sentinel enters viewport. If `false`, plays only once.
|
|
131
|
+
- **`debug`** - If `true`, displays the sentinel element as a visible cyan line below your element. Useful for seeing exactly when animations trigger.
|
|
132
|
+
- **`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.
|
|
133
|
+
|
|
99
134
|
### Examples
|
|
100
135
|
|
|
101
136
|
```svelte
|
|
@@ -114,9 +149,35 @@ interface RuneScrollerOptions {
|
|
|
114
149
|
Repeats every time you scroll
|
|
115
150
|
</div>
|
|
116
151
|
|
|
117
|
-
<!-- Debug mode
|
|
152
|
+
<!-- Debug mode - shows cyan line marking sentinel position -->
|
|
118
153
|
<div use:runeScroller={{ animation: 'fade-in', debug: true }}>
|
|
119
|
-
|
|
154
|
+
The cyan line below this shows when animation will trigger
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
<!-- Multiple options -->
|
|
158
|
+
<div use:runeScroller={{
|
|
159
|
+
animation: 'fade-in-up',
|
|
160
|
+
duration: 1200,
|
|
161
|
+
repeat: true,
|
|
162
|
+
debug: true
|
|
163
|
+
}}>
|
|
164
|
+
Full featured example
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<!-- Large element - trigger animation earlier with negative offset -->
|
|
168
|
+
<div use:runeScroller={{
|
|
169
|
+
animation: 'fade-in-up',
|
|
170
|
+
offset: -200 // Trigger 200px before element bottom
|
|
171
|
+
}}>
|
|
172
|
+
Large content that needs early triggering
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
<!-- Delay animation by moving sentinel down -->
|
|
176
|
+
<div use:runeScroller={{
|
|
177
|
+
animation: 'zoom-in',
|
|
178
|
+
offset: 300 // Trigger 300px after element bottom
|
|
179
|
+
}}>
|
|
180
|
+
Content with delayed animation
|
|
120
181
|
</div>
|
|
121
182
|
```
|
|
122
183
|
|
|
@@ -194,14 +255,25 @@ Rune Scroller uses **sentinel-based triggering**:
|
|
|
194
255
|
|
|
195
256
|
## 🌐 SSR Compatibility
|
|
196
257
|
|
|
197
|
-
Works seamlessly with SvelteKit:
|
|
258
|
+
Works seamlessly with SvelteKit. Import CSS in your root layout:
|
|
198
259
|
|
|
199
260
|
```svelte
|
|
261
|
+
<!-- src/routes/+layout.svelte -->
|
|
200
262
|
<script>
|
|
201
|
-
import runeScroller from 'rune-scroller';
|
|
202
263
|
import 'rune-scroller/animations.css';
|
|
203
264
|
</script>
|
|
204
265
|
|
|
266
|
+
<slot />
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Then use animations anywhere in your app:
|
|
270
|
+
|
|
271
|
+
```svelte
|
|
272
|
+
<!-- src/routes/+page.svelte -->
|
|
273
|
+
<script>
|
|
274
|
+
import runeScroller from 'rune-scroller';
|
|
275
|
+
</script>
|
|
276
|
+
|
|
205
277
|
<!-- No special handling needed -->
|
|
206
278
|
<div use:runeScroller={{ animation: 'fade-in-up' }}>
|
|
207
279
|
Works in SvelteKit SSR!
|
|
@@ -270,6 +342,7 @@ interface RuneScrollerOptions {
|
|
|
270
342
|
duration?: number;
|
|
271
343
|
repeat?: boolean;
|
|
272
344
|
debug?: boolean;
|
|
345
|
+
offset?: number;
|
|
273
346
|
}
|
|
274
347
|
|
|
275
348
|
interface AnimateOptions {
|
package/dist/animations.css
CHANGED
|
@@ -21,6 +21,16 @@
|
|
|
21
21
|
/* Animation states - transform-specific initial states */
|
|
22
22
|
/* (opacity: 0 is already set in .scroll-animate base class) */
|
|
23
23
|
|
|
24
|
+
/* Fade In (no transform, just opacity) */
|
|
25
|
+
[data-animation='fade-in'] {
|
|
26
|
+
/* No transform needed, uses base opacity: 0 from .scroll-animate */
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
[data-animation='fade-in'].is-visible {
|
|
30
|
+
/* Inherits opacity: 1 from .scroll-animate.is-visible */
|
|
31
|
+
transform: none;
|
|
32
|
+
}
|
|
33
|
+
|
|
24
34
|
/* Fade In Up */
|
|
25
35
|
[data-animation='fade-in-up'] {
|
|
26
36
|
transform: translateY(300px);
|
|
@@ -14,9 +14,10 @@ export declare function setCSSVariables(element: HTMLElement, duration?: number,
|
|
|
14
14
|
export declare function setupAnimationElement(element: HTMLElement, animation: AnimationType): void;
|
|
15
15
|
/**
|
|
16
16
|
* Create sentinel element for observer-based triggering
|
|
17
|
-
* Positioned absolutely
|
|
18
|
-
* @param element - Reference element (used to position sentinel
|
|
17
|
+
* Positioned absolutely relative to element (no layout impact)
|
|
18
|
+
* @param element - Reference element (used to position sentinel)
|
|
19
19
|
* @param debug - If true, shows the sentinel as a visible line for debugging
|
|
20
|
+
* @param offset - Offset in pixels from element bottom (default: 0, negative = above element)
|
|
20
21
|
* @returns The created sentinel element
|
|
21
22
|
*/
|
|
22
|
-
export declare function createSentinel(element: HTMLElement, debug?: boolean): HTMLElement;
|
|
23
|
+
export declare function createSentinel(element: HTMLElement, debug?: boolean, offset?: number): HTMLElement;
|
package/dist/dom-utils.svelte.js
CHANGED
|
@@ -21,26 +21,28 @@ export function setupAnimationElement(element, animation) {
|
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
23
|
* Create sentinel element for observer-based triggering
|
|
24
|
-
* Positioned absolutely
|
|
25
|
-
* @param element - Reference element (used to position sentinel
|
|
24
|
+
* Positioned absolutely relative to element (no layout impact)
|
|
25
|
+
* @param element - Reference element (used to position sentinel)
|
|
26
26
|
* @param debug - If true, shows the sentinel as a visible line for debugging
|
|
27
|
+
* @param offset - Offset in pixels from element bottom (default: 0, negative = above element)
|
|
27
28
|
* @returns The created sentinel element
|
|
28
29
|
*/
|
|
29
|
-
export function createSentinel(element, debug = false) {
|
|
30
|
+
export function createSentinel(element, debug = false, offset = 0) {
|
|
30
31
|
const sentinel = document.createElement('div');
|
|
31
|
-
// Get element dimensions to position sentinel at its bottom
|
|
32
|
+
// Get element dimensions to position sentinel at its bottom + offset
|
|
32
33
|
const rect = element.getBoundingClientRect();
|
|
33
34
|
const elementHeight = rect.height;
|
|
35
|
+
const sentinelTop = elementHeight + offset;
|
|
34
36
|
if (debug) {
|
|
35
37
|
// Debug mode: visible primary color line (cyan #00e0ff)
|
|
36
38
|
sentinel.style.cssText =
|
|
37
|
-
`position:absolute;top:${
|
|
39
|
+
`position:absolute;top:${sentinelTop}px;left:0;right:0;height:3px;background:#00e0ff;margin:0;padding:0;box-sizing:border-box;z-index:999;pointer-events:none`;
|
|
38
40
|
sentinel.setAttribute('data-sentinel-debug', 'true');
|
|
39
41
|
}
|
|
40
42
|
else {
|
|
41
43
|
// Production: invisible positioned absolutely (no layout impact)
|
|
42
44
|
sentinel.style.cssText =
|
|
43
|
-
`position:absolute;top:${
|
|
45
|
+
`position:absolute;top:${sentinelTop}px;left:0;right:0;height:1px;visibility:hidden;margin:0;padding:0;box-sizing:border-box;pointer-events:none`;
|
|
44
46
|
}
|
|
45
47
|
return sentinel;
|
|
46
48
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type { RuneScrollerOptions, AnimateOptions, IntersectionOptions, UseIntersectionReturn } from './types';
|
|
2
2
|
export type { AnimationType } from './animations';
|
|
3
|
-
export { runeScroller as default } from './runeScroller.svelte
|
|
4
|
-
export { animate } from './animate.svelte
|
|
5
|
-
export { useIntersection, useIntersectionOnce } from './useIntersection.svelte
|
|
3
|
+
export { runeScroller as default } from './runeScroller.svelte';
|
|
4
|
+
export { animate } from './animate.svelte';
|
|
5
|
+
export { useIntersection, useIntersectionOnce } from './useIntersection.svelte';
|
|
6
6
|
export { calculateRootMargin } from './animations';
|
|
@@ -24,19 +24,17 @@ export function runeScroller(element, options) {
|
|
|
24
24
|
setupAnimationElement(element, options.animation);
|
|
25
25
|
setCSSVariables(element, options.duration);
|
|
26
26
|
}
|
|
27
|
+
// Créer un wrapper div autour de l'élément pour le sentinel en position absolute
|
|
28
|
+
// Ceci évite de casser le flex/grid flow du parent
|
|
29
|
+
const wrapper = document.createElement('div');
|
|
30
|
+
wrapper.style.cssText = 'position:relative;display:contents';
|
|
31
|
+
// Insérer le wrapper avant l'élément
|
|
32
|
+
element.insertAdjacentElement('beforebegin', wrapper);
|
|
33
|
+
wrapper.appendChild(element);
|
|
27
34
|
// Créer le sentinel invisible (ou visible si debug=true)
|
|
28
|
-
// Sentinel positioned absolutely relative to
|
|
29
|
-
const sentinel = createSentinel(element, options?.debug);
|
|
30
|
-
|
|
31
|
-
if (parent) {
|
|
32
|
-
// Ensure parent has position context for absolute positioning
|
|
33
|
-
const parentPosition = window.getComputedStyle(parent).position;
|
|
34
|
-
if (parentPosition === 'static') {
|
|
35
|
-
parent.style.position = 'relative';
|
|
36
|
-
}
|
|
37
|
-
// Insert sentinel after element, positioned absolutely
|
|
38
|
-
element.insertAdjacentElement('afterend', sentinel);
|
|
39
|
-
}
|
|
35
|
+
// Sentinel positioned absolutely relative to wrapper
|
|
36
|
+
const sentinel = createSentinel(element, options?.debug, options?.offset);
|
|
37
|
+
wrapper.appendChild(sentinel);
|
|
40
38
|
// Observer le sentinel avec cleanup tracking
|
|
41
39
|
let observerConnected = true;
|
|
42
40
|
const observer = new IntersectionObserver((entries) => {
|
|
@@ -74,6 +72,12 @@ export function runeScroller(element, options) {
|
|
|
74
72
|
observer.disconnect();
|
|
75
73
|
}
|
|
76
74
|
sentinel.remove();
|
|
75
|
+
// Unwrap element (move it out of wrapper)
|
|
76
|
+
const parent = wrapper.parentElement;
|
|
77
|
+
if (parent) {
|
|
78
|
+
wrapper.insertAdjacentElement('beforebegin', element);
|
|
79
|
+
}
|
|
80
|
+
wrapper.remove();
|
|
77
81
|
}
|
|
78
82
|
};
|
|
79
83
|
}
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rune-scroller",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Lightweight, high-performance scroll animations for Svelte 5. ~2KB bundle, zero dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -40,6 +40,19 @@
|
|
|
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
|
+
},
|
|
43
56
|
"devDependencies": {
|
|
44
57
|
"@eslint/compat": "^1.4.0",
|
|
45
58
|
"@eslint/js": "^9.36.0",
|
|
@@ -59,17 +72,5 @@
|
|
|
59
72
|
"typescript-eslint": "^8.44.1",
|
|
60
73
|
"vite": "^7.1.7",
|
|
61
74
|
"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"
|
|
74
75
|
}
|
|
75
|
-
}
|
|
76
|
+
}
|