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 +208 -477
- package/package.json +6 -3
- package/dist/animations.css +0 -83
- package/dist/animations.d.ts +0 -18
- package/dist/animations.js +0 -38
- package/dist/dom-utils.d.ts +0 -30
- package/dist/dom-utils.js +0 -112
- package/dist/index.d.ts +0 -5
- package/dist/index.js +0 -23
- package/dist/observer-utils.d.ts +0 -21
- package/dist/observer-utils.js +0 -31
- package/dist/runeScroller.d.ts +0 -9
- package/dist/runeScroller.js +0 -166
- package/dist/types.d.ts +0 -78
- package/dist/types.js +0 -48
- package/dist/useIntersection.svelte.d.ts +0 -11
- package/dist/useIntersection.svelte.js +0 -90
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rune-scroller",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Lightweight
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "Lightweight scroll animations for Svelte 5. Drop-in AOS replacement. 30 animations, zero dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"license": "MIT",
|
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
"svelte": "./dist/index.js",
|
|
30
30
|
"default": "./dist/index.js"
|
|
31
31
|
},
|
|
32
|
+
"./aos": {
|
|
33
|
+
"default": "./dist/aos.js"
|
|
34
|
+
},
|
|
32
35
|
"./animations.css": "./dist/animations.css"
|
|
33
36
|
},
|
|
34
37
|
"files": [
|
|
@@ -59,7 +62,7 @@
|
|
|
59
62
|
"@eslint/compat": "^1.4.0",
|
|
60
63
|
"@eslint/js": "^9.36.0",
|
|
61
64
|
"@sveltejs/kit": "^2.43.2",
|
|
62
|
-
"@sveltejs/package": "^2.5.
|
|
65
|
+
"@sveltejs/package": "^2.5.7",
|
|
63
66
|
"@sveltejs/vite-plugin-svelte": "^6.2.0",
|
|
64
67
|
"@types/node": "^22",
|
|
65
68
|
"eslint": "^9.36.0",
|
package/dist/animations.css
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Reusable scroll animation styles - Optimized with CSS custom properties
|
|
3
|
-
* Reduces bundle size while maintaining all 14 animations
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/* Base animation container with optimized transitions */
|
|
7
|
-
.scroll-animate,
|
|
8
|
-
.animated-element {
|
|
9
|
-
opacity: 0;
|
|
10
|
-
transition: opacity var(--duration, 2500ms) linear var(--delay, 0ms),
|
|
11
|
-
transform var(--duration, 2500ms) cubic-bezier(0.34, 1.56, 0.64, 1) var(--delay, 0ms);
|
|
12
|
-
transform: perspective(1000px) translate(var(--tx, 0), var(--ty, 0)) scale(var(--scale, 1)) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)) rotate(var(--rotate, 0deg));
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
.scroll-animate.is-visible,
|
|
16
|
-
.animated-element.is-visible {
|
|
17
|
-
opacity: 1 !important;
|
|
18
|
-
will-change: transform, opacity;
|
|
19
|
-
transform: perspective(1000px) translate(var(--tx, 0), var(--ty, 0)) scale(var(--scale, 1)) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)) rotate(var(--rotate, 0deg));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/* Initial state - transform values before animation */
|
|
23
|
-
[data-animation='fade-in'] { --tx: 0; --ty: 0; }
|
|
24
|
-
[data-animation='fade-in-up'] { --ty: var(--translate-distance, 300px); }
|
|
25
|
-
[data-animation='fade-in-down'] { --ty: calc(-1 * var(--translate-distance, 300px)); }
|
|
26
|
-
[data-animation='fade-in-left'] { --tx: calc(-1 * var(--translate-distance, 300px)); }
|
|
27
|
-
[data-animation='fade-in-right'] { --tx: var(--translate-distance, 300px); }
|
|
28
|
-
|
|
29
|
-
[data-animation='zoom-in'] { --scale: 0.3; }
|
|
30
|
-
[data-animation='zoom-out'] { --scale: 2; }
|
|
31
|
-
[data-animation='zoom-in-up'] { --scale: 0.5; --ty: var(--translate-distance, 300px); }
|
|
32
|
-
[data-animation='zoom-in-left'] { --scale: 0.5; --tx: calc(-1 * var(--translate-distance, 300px)); }
|
|
33
|
-
[data-animation='zoom-in-right'] { --scale: 0.5; --tx: var(--translate-distance, 300px); }
|
|
34
|
-
|
|
35
|
-
[data-animation='flip'] { --ry: 90deg; }
|
|
36
|
-
[data-animation='flip-x'] { --rx: 90deg; }
|
|
37
|
-
|
|
38
|
-
[data-animation='slide-rotate'] { --tx: calc(-1 * var(--translate-distance, 300px)); --rotate: -45deg; }
|
|
39
|
-
[data-animation='bounce-in'] { --scale: 0; }
|
|
40
|
-
|
|
41
|
-
/* Visible state - reset transform values to final position */
|
|
42
|
-
[data-animation='fade-in-up'].is-visible { --ty: 0; }
|
|
43
|
-
[data-animation='fade-in-down'].is-visible { --ty: 0; }
|
|
44
|
-
[data-animation='fade-in-left'].is-visible { --tx: 0; }
|
|
45
|
-
[data-animation='fade-in-right'].is-visible { --tx: 0; }
|
|
46
|
-
|
|
47
|
-
[data-animation='zoom-in'].is-visible { --scale: 1; }
|
|
48
|
-
[data-animation='zoom-out'].is-visible { --scale: 1; }
|
|
49
|
-
[data-animation='zoom-in-up'].is-visible { --scale: 1; --ty: 0; }
|
|
50
|
-
[data-animation='zoom-in-left'].is-visible { --scale: 1; --tx: 0; }
|
|
51
|
-
[data-animation='zoom-in-right'].is-visible { --scale: 1; --tx: 0; }
|
|
52
|
-
|
|
53
|
-
[data-animation='flip'].is-visible { --ry: 0deg; }
|
|
54
|
-
[data-animation='flip-x'].is-visible { --rx: 0deg; }
|
|
55
|
-
|
|
56
|
-
[data-animation='slide-rotate'].is-visible { --tx: 0; --rotate: 0deg; }
|
|
57
|
-
|
|
58
|
-
@keyframes bounce {
|
|
59
|
-
0% { transform: scale(0); }
|
|
60
|
-
50% { transform: scale(1.1); }
|
|
61
|
-
100% { transform: scale(1); }
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/* Bounce animation - special case with keyframes */
|
|
65
|
-
[data-animation='bounce-in'].is-visible {
|
|
66
|
-
animation: bounce var(--duration, 1500ms) cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
|
67
|
-
animation-delay: var(--delay, 0ms);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/* Accessibility: Respect user's motion preferences */
|
|
71
|
-
@media (prefers-reduced-motion: reduce) {
|
|
72
|
-
.scroll-animate,
|
|
73
|
-
.animated-element {
|
|
74
|
-
transition: none;
|
|
75
|
-
animation: none !important;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
.scroll-animate.is-visible,
|
|
79
|
-
.animated-element.is-visible {
|
|
80
|
-
opacity: 1;
|
|
81
|
-
transform: none !important;
|
|
82
|
-
}
|
|
83
|
-
}
|
package/dist/animations.d.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Calculate rootMargin for IntersectionObserver from offset or custom rootMargin
|
|
3
|
-
*
|
|
4
|
-
* @param {number} [offset] - Viewport offset (0-100). 0 = bottom of viewport touches top of element, 100 = top of viewport touches top of element
|
|
5
|
-
* @param {string} [rootMargin] - Custom rootMargin string (takes precedence over offset)
|
|
6
|
-
* @returns {string} rootMargin string for IntersectionObserver
|
|
7
|
-
*/
|
|
8
|
-
export function calculateRootMargin(offset?: number, rootMargin?: string): string;
|
|
9
|
-
/**
|
|
10
|
-
* Animation utilities
|
|
11
|
-
* Type definitions have been moved to types.js for single source of truth
|
|
12
|
-
*/
|
|
13
|
-
/**
|
|
14
|
-
* All available animation types in the library
|
|
15
|
-
* Useful for programmatic access and validation
|
|
16
|
-
* @type {readonly string[]}
|
|
17
|
-
*/
|
|
18
|
-
export const ANIMATION_TYPES: readonly string[];
|
package/dist/animations.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Animation utilities
|
|
3
|
-
* Type definitions have been moved to types.js for single source of truth
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* All available animation types in the library
|
|
8
|
-
* Useful for programmatic access and validation
|
|
9
|
-
* @type {readonly string[]}
|
|
10
|
-
*/
|
|
11
|
-
export const ANIMATION_TYPES = [
|
|
12
|
-
'fade-in',
|
|
13
|
-
'fade-in-up',
|
|
14
|
-
'fade-in-down',
|
|
15
|
-
'fade-in-left',
|
|
16
|
-
'fade-in-right',
|
|
17
|
-
'zoom-in',
|
|
18
|
-
'zoom-out',
|
|
19
|
-
'zoom-in-up',
|
|
20
|
-
'zoom-in-left',
|
|
21
|
-
'zoom-in-right',
|
|
22
|
-
'flip',
|
|
23
|
-
'flip-x',
|
|
24
|
-
'slide-rotate',
|
|
25
|
-
'bounce-in'
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Calculate rootMargin for IntersectionObserver from offset or custom rootMargin
|
|
30
|
-
*
|
|
31
|
-
* @param {number} [offset] - Viewport offset (0-100). 0 = bottom of viewport touches top of element, 100 = top of viewport touches top of element
|
|
32
|
-
* @param {string} [rootMargin] - Custom rootMargin string (takes precedence over offset)
|
|
33
|
-
* @returns {string} rootMargin string for IntersectionObserver
|
|
34
|
-
*/
|
|
35
|
-
export function calculateRootMargin(offset, rootMargin) {
|
|
36
|
-
return rootMargin ??
|
|
37
|
-
(offset !== undefined ? `-${100 - offset}% 0px -${offset}% 0px` : '-10% 0px -10% 0px');
|
|
38
|
-
}
|
package/dist/dom-utils.d.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @param {HTMLElement} element
|
|
3
|
-
* @param {number} [duration]
|
|
4
|
-
* @param {number} [delay=0]
|
|
5
|
-
*/
|
|
6
|
-
export function setCSSVariables(element: HTMLElement, duration?: number, delay?: number): void;
|
|
7
|
-
/**
|
|
8
|
-
* @param {HTMLElement} element
|
|
9
|
-
* @param {import('./types.js').AnimationType} animation
|
|
10
|
-
*/
|
|
11
|
-
export function setupAnimationElement(element: HTMLElement, animation: import("./types.js").AnimationType): void;
|
|
12
|
-
/**
|
|
13
|
-
* @param {HTMLElement} element
|
|
14
|
-
* @param {boolean} [debug=false]
|
|
15
|
-
* @param {number} [offset=0]
|
|
16
|
-
* @param {string} [sentinelColor='#00e0ff']
|
|
17
|
-
* @param {string} [debugLabel]
|
|
18
|
-
* @param {string} [sentinelId]
|
|
19
|
-
* @returns {{ element: HTMLElement, id: string }}
|
|
20
|
-
*/
|
|
21
|
-
export function createSentinel(element: HTMLElement, debug?: boolean, offset?: number, sentinelColor?: string, debugLabel?: string, sentinelId?: string): {
|
|
22
|
-
element: HTMLElement;
|
|
23
|
-
id: string;
|
|
24
|
-
};
|
|
25
|
-
/**
|
|
26
|
-
* Check if CSS animations are loaded and warn if not (dev only)
|
|
27
|
-
* Uses cache to avoid expensive getComputedStyle() on every element creation
|
|
28
|
-
* @returns {boolean} True if CSS appears to be loaded
|
|
29
|
-
*/
|
|
30
|
-
export function checkAndWarnIfCSSNotLoaded(): boolean;
|
package/dist/dom-utils.js
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Global counter for auto-generating sentinel IDs
|
|
3
|
-
* @type {number}
|
|
4
|
-
*/
|
|
5
|
-
let sentinelCounter = 0;
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Cache to check CSS only once per page load
|
|
9
|
-
* Avoids expensive getComputedStyle() calls
|
|
10
|
-
* @type {boolean | null}
|
|
11
|
-
*/
|
|
12
|
-
let cssCheckResult = null;
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* @param {HTMLElement} element
|
|
16
|
-
* @param {number} [duration]
|
|
17
|
-
* @param {number} [delay=0]
|
|
18
|
-
*/
|
|
19
|
-
export function setCSSVariables(element, duration, delay = 0) {
|
|
20
|
-
if (duration !== undefined) {
|
|
21
|
-
element.style.setProperty('--duration', `${duration}ms`);
|
|
22
|
-
}
|
|
23
|
-
element.style.setProperty('--delay', `${delay}ms`);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* @param {HTMLElement} element
|
|
28
|
-
* @param {import('./types.js').AnimationType} animation
|
|
29
|
-
*/
|
|
30
|
-
export function setupAnimationElement(element, animation) {
|
|
31
|
-
element.classList.add('scroll-animate');
|
|
32
|
-
element.setAttribute('data-animation', animation);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* @param {HTMLElement} element
|
|
37
|
-
* @param {boolean} [debug=false]
|
|
38
|
-
* @param {number} [offset=0]
|
|
39
|
-
* @param {string} [sentinelColor='#00e0ff']
|
|
40
|
-
* @param {string} [debugLabel]
|
|
41
|
-
* @param {string} [sentinelId]
|
|
42
|
-
* @returns {{ element: HTMLElement, id: string }}
|
|
43
|
-
*/
|
|
44
|
-
export function createSentinel(element, debug = false, offset = 0, sentinelColor = '#00e0ff', debugLabel = '', sentinelId) {
|
|
45
|
-
const sentinel = document.createElement('div');
|
|
46
|
-
// Use offsetHeight instead of getBoundingClientRect for accurate dimensions
|
|
47
|
-
// getBoundingClientRect returns transformed dimensions (affected by scale, etc)
|
|
48
|
-
// offsetHeight returns actual element height independent of CSS transforms
|
|
49
|
-
const elementHeight = element.offsetHeight;
|
|
50
|
-
const sentinelTop = elementHeight + offset;
|
|
51
|
-
|
|
52
|
-
// Generate auto-ID if not provided
|
|
53
|
-
if (!sentinelId) {
|
|
54
|
-
sentinelCounter++;
|
|
55
|
-
sentinelId = `sentinel-${sentinelCounter}`;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Always set to data-sentinel-id attribute
|
|
59
|
-
sentinel.setAttribute('data-sentinel-id', sentinelId);
|
|
60
|
-
|
|
61
|
-
if (debug) {
|
|
62
|
-
sentinel.style.cssText =
|
|
63
|
-
`position:absolute;top:${sentinelTop}px;left:0;width:100%;height:3px;background:${sentinelColor};margin:0;padding:2px 4px;box-sizing:border-box;z-index:999;pointer-events:none;display:flex;align-items:center;font-size:10px;color:#000;font-weight:bold;white-space:nowrap;overflow:hidden;text-overflow:ellipsis`;
|
|
64
|
-
sentinel.setAttribute('data-sentinel-debug', 'true');
|
|
65
|
-
// Show ID in debug mode (or debugLabel if provided)
|
|
66
|
-
if (debugLabel) {
|
|
67
|
-
sentinel.textContent = debugLabel;
|
|
68
|
-
} else {
|
|
69
|
-
sentinel.textContent = sentinelId;
|
|
70
|
-
}
|
|
71
|
-
} else {
|
|
72
|
-
sentinel.style.cssText =
|
|
73
|
-
`position:absolute;top:${sentinelTop}px;left:0;width:100%;height:1px;visibility:hidden;margin:0;padding:0;box-sizing:border-box;pointer-events:none`;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return { element: sentinel, id: sentinelId };
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Check if CSS animations are loaded and warn if not (dev only)
|
|
81
|
-
* Uses cache to avoid expensive getComputedStyle() on every element creation
|
|
82
|
-
* @returns {boolean} True if CSS appears to be loaded
|
|
83
|
-
*/
|
|
84
|
-
export function checkAndWarnIfCSSNotLoaded() {
|
|
85
|
-
if (typeof document === 'undefined') return false;
|
|
86
|
-
if (process.env.NODE_ENV === 'production') return true;
|
|
87
|
-
|
|
88
|
-
// Return cached result if already checked (avoids expensive reflows)
|
|
89
|
-
if (cssCheckResult !== null) return cssCheckResult;
|
|
90
|
-
|
|
91
|
-
// Try to detect if animations.css is loaded by checking for animation classes
|
|
92
|
-
const test = document.createElement('div');
|
|
93
|
-
test.className = 'scroll-animate is-visible';
|
|
94
|
-
test.style.position = 'absolute';
|
|
95
|
-
test.style.opacity = '0';
|
|
96
|
-
document.body.appendChild(test);
|
|
97
|
-
const computed = getComputedStyle(test);
|
|
98
|
-
const hasAnimation = computed.animation !== 'none' && computed.animation !== '';
|
|
99
|
-
test.remove();
|
|
100
|
-
|
|
101
|
-
if (!hasAnimation) {
|
|
102
|
-
console.warn(
|
|
103
|
-
'[rune-scroller] CSS animations not found. Make sure to import the animations:\n' +
|
|
104
|
-
' import "rune-scroller/animations.css";\n' +
|
|
105
|
-
'Documentation: https://github.com/lelabdev/rune-scroller#installation'
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Cache the result for future calls
|
|
110
|
-
cssCheckResult = hasAnimation;
|
|
111
|
-
return hasAnimation;
|
|
112
|
-
}
|
package/dist/index.d.ts
DELETED
package/dist/index.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Rune Scroller - Lightweight scroll animations for Svelte 5
|
|
3
|
-
*
|
|
4
|
-
* Main entry point exporting all public APIs
|
|
5
|
-
*
|
|
6
|
-
* @module rune-scroller
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
// Import CSS animations automatically
|
|
10
|
-
import './animations.css';
|
|
11
|
-
|
|
12
|
-
// Main action (default export - recommended)
|
|
13
|
-
import { runeScroller } from './runeScroller.js';
|
|
14
|
-
export default runeScroller;
|
|
15
|
-
export { runeScroller };
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
// Composables
|
|
20
|
-
export { useIntersection, useIntersectionOnce } from './useIntersection.svelte.js';
|
|
21
|
-
|
|
22
|
-
// Utilities
|
|
23
|
-
export { calculateRootMargin } from './animations.js';
|
package/dist/observer-utils.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared IntersectionObserver utility functions
|
|
3
|
-
* Reduces code duplication between action implementations
|
|
4
|
-
*/
|
|
5
|
-
/**
|
|
6
|
-
* @param {HTMLElement} target
|
|
7
|
-
* @param {IntersectionObserverCallback} callback
|
|
8
|
-
* @param {IntersectionObserverInit} options
|
|
9
|
-
* @returns {{ observer: IntersectionObserver, isConnected: boolean }}
|
|
10
|
-
*/
|
|
11
|
-
export function createManagedObserver(target: HTMLElement, callback: IntersectionObserverCallback, options: IntersectionObserverInit): {
|
|
12
|
-
observer: IntersectionObserver;
|
|
13
|
-
isConnected: boolean;
|
|
14
|
-
};
|
|
15
|
-
/**
|
|
16
|
-
* @param {IntersectionObserver} observer
|
|
17
|
-
* @param {{ isConnected: boolean }} state
|
|
18
|
-
*/
|
|
19
|
-
export function disconnectObserver(observer: IntersectionObserver, state: {
|
|
20
|
-
isConnected: boolean;
|
|
21
|
-
}): void;
|
package/dist/observer-utils.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared IntersectionObserver utility functions
|
|
3
|
-
* Reduces code duplication between action implementations
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @param {HTMLElement} target
|
|
8
|
-
* @param {IntersectionObserverCallback} callback
|
|
9
|
-
* @param {IntersectionObserverInit} options
|
|
10
|
-
* @returns {{ observer: IntersectionObserver, isConnected: boolean }}
|
|
11
|
-
*/
|
|
12
|
-
export function createManagedObserver(target, callback, options) {
|
|
13
|
-
const observer = new IntersectionObserver(callback, options);
|
|
14
|
-
observer.observe(target);
|
|
15
|
-
|
|
16
|
-
return {
|
|
17
|
-
observer,
|
|
18
|
-
isConnected: true
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* @param {IntersectionObserver} observer
|
|
24
|
-
* @param {{ isConnected: boolean }} state
|
|
25
|
-
*/
|
|
26
|
-
export function disconnectObserver(observer, state) {
|
|
27
|
-
if (state.isConnected && observer) {
|
|
28
|
-
observer.disconnect();
|
|
29
|
-
state.isConnected = false;
|
|
30
|
-
}
|
|
31
|
-
}
|
package/dist/runeScroller.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @param {HTMLElement} element
|
|
3
|
-
* @param {import('./types.js').RuneScrollerOptions} [options]
|
|
4
|
-
* @returns {{ update: (newOptions?: import('./types.js').RuneScrollerOptions) => void, destroy: () => void }}
|
|
5
|
-
*/
|
|
6
|
-
export function runeScroller(element: HTMLElement, options?: import("./types.js").RuneScrollerOptions): {
|
|
7
|
-
update: (newOptions?: import("./types.js").RuneScrollerOptions) => void;
|
|
8
|
-
destroy: () => void;
|
|
9
|
-
};
|
package/dist/runeScroller.js
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import { setCSSVariables, setupAnimationElement, createSentinel, checkAndWarnIfCSSNotLoaded } from './dom-utils.js';
|
|
2
|
-
import { createManagedObserver, disconnectObserver } from './observer-utils.js';
|
|
3
|
-
import { ANIMATION_TYPES } from './animations.js';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @param {HTMLElement} element
|
|
7
|
-
* @param {import('./types.js').RuneScrollerOptions} [options]
|
|
8
|
-
* @returns {{ update: (newOptions?: import('./types.js').RuneScrollerOptions) => void, destroy: () => void }}
|
|
9
|
-
*/
|
|
10
|
-
export function runeScroller(element, options) {
|
|
11
|
-
// SSR Guard: Return no-op action when running on server
|
|
12
|
-
if (typeof window === 'undefined') {
|
|
13
|
-
return {
|
|
14
|
-
update: () => {},
|
|
15
|
-
destroy: () => {}
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Warn if CSS is not loaded (first time only)
|
|
20
|
-
if (typeof document !== 'undefined') {
|
|
21
|
-
checkAndWarnIfCSSNotLoaded();
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Validate animation type
|
|
25
|
-
let animation = options?.animation ?? 'fade-in';
|
|
26
|
-
if (animation && !ANIMATION_TYPES.includes(animation)) {
|
|
27
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
28
|
-
console.warn(
|
|
29
|
-
`[rune-scroller] Invalid animation "${animation}". Using "fade-in" instead. ` +
|
|
30
|
-
`Valid options: ${ANIMATION_TYPES.join(', ')}`
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
animation = 'fade-in';
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Initialize opacity: 0 BEFORE adding .scroll-animate class
|
|
37
|
-
// This ensures the transition applies correctly when .is-visible is added later
|
|
38
|
-
element.style.opacity = '0';
|
|
39
|
-
|
|
40
|
-
// Setup animation classes and CSS variables
|
|
41
|
-
if (animation) {
|
|
42
|
-
setupAnimationElement(element, animation);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Set CSS variables for duration
|
|
46
|
-
if (options?.duration !== undefined) {
|
|
47
|
-
setCSSVariables(element, options.duration);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Force reflow to ensure transitions are ready
|
|
51
|
-
void element.offsetHeight;
|
|
52
|
-
|
|
53
|
-
// Create a wrapper div around the element to position the sentinel
|
|
54
|
-
const wrapper = document.createElement('div');
|
|
55
|
-
wrapper.style.cssText = 'position:relative;display:block;width:100%;margin:0;padding:0;box-sizing:border-box';
|
|
56
|
-
element.insertAdjacentElement('beforebegin', wrapper);
|
|
57
|
-
wrapper.appendChild(element);
|
|
58
|
-
|
|
59
|
-
// Create the invisible sentinel (or visible if debug=true)
|
|
60
|
-
// Positioned absolutely relative to the wrapper
|
|
61
|
-
const sentinelResult = createSentinel(
|
|
62
|
-
element,
|
|
63
|
-
options?.debug,
|
|
64
|
-
options?.offset,
|
|
65
|
-
options?.sentinelColor,
|
|
66
|
-
options?.debugLabel,
|
|
67
|
-
options?.sentinelId
|
|
68
|
-
);
|
|
69
|
-
const sentinel = sentinelResult.element;
|
|
70
|
-
const sentinelId = sentinelResult.id;
|
|
71
|
-
|
|
72
|
-
// Add sentinel ID to element (either provided or auto-generated)
|
|
73
|
-
element.setAttribute('data-sentinel-id', sentinelId);
|
|
74
|
-
|
|
75
|
-
wrapper.appendChild(sentinel);
|
|
76
|
-
|
|
77
|
-
// Observe the sentinel with cleanup tracking
|
|
78
|
-
const state = { isConnected: true };
|
|
79
|
-
let currentSentinel = sentinel;
|
|
80
|
-
let resizeObserver;
|
|
81
|
-
let intersectionObserver;
|
|
82
|
-
|
|
83
|
-
const { observer } = createManagedObserver(
|
|
84
|
-
sentinel,
|
|
85
|
-
(entries) => {
|
|
86
|
-
const isIntersecting = entries[0].isIntersecting;
|
|
87
|
-
if (isIntersecting) {
|
|
88
|
-
// Add the is-visible class to trigger animation
|
|
89
|
-
element.classList.add('is-visible');
|
|
90
|
-
// Call onVisible callback if provided
|
|
91
|
-
options?.onVisible?.(element);
|
|
92
|
-
// Disconnect if not in repeat mode
|
|
93
|
-
if (!options?.repeat) {
|
|
94
|
-
disconnectObserver(intersectionObserver, state);
|
|
95
|
-
}
|
|
96
|
-
} else if (options?.repeat) {
|
|
97
|
-
// In repeat mode, remove the class when the sentinel exits
|
|
98
|
-
element.classList.remove('is-visible');
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
{ threshold: 0 }
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
intersectionObserver = observer;
|
|
105
|
-
|
|
106
|
-
// Function to recreate sentinel when element is resized
|
|
107
|
-
const recreateSentinel = () => {
|
|
108
|
-
const newSentinelResult = createSentinel(
|
|
109
|
-
element,
|
|
110
|
-
options?.debug,
|
|
111
|
-
options?.offset,
|
|
112
|
-
options?.sentinelColor,
|
|
113
|
-
options?.debugLabel,
|
|
114
|
-
sentinelId
|
|
115
|
-
);
|
|
116
|
-
const newSentinel = newSentinelResult.element;
|
|
117
|
-
currentSentinel.replaceWith(newSentinel);
|
|
118
|
-
currentSentinel = newSentinel;
|
|
119
|
-
// Update observer to watch the new sentinel
|
|
120
|
-
intersectionObserver.disconnect();
|
|
121
|
-
intersectionObserver.observe(newSentinel);
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
// Setup ResizeObserver to handle element resizing
|
|
125
|
-
if (typeof ResizeObserver !== 'undefined') {
|
|
126
|
-
resizeObserver = new ResizeObserver(() => {
|
|
127
|
-
recreateSentinel();
|
|
128
|
-
});
|
|
129
|
-
resizeObserver.observe(element);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return {
|
|
133
|
-
update(newOptions) {
|
|
134
|
-
if (newOptions?.animation) {
|
|
135
|
-
element.setAttribute('data-animation', newOptions.animation);
|
|
136
|
-
}
|
|
137
|
-
if (newOptions?.duration) {
|
|
138
|
-
setCSSVariables(element, newOptions.duration);
|
|
139
|
-
}
|
|
140
|
-
// Update repeat option
|
|
141
|
-
if (newOptions?.repeat !== undefined && newOptions.repeat !== options?.repeat) {
|
|
142
|
-
options = { ...options, repeat: newOptions.repeat };
|
|
143
|
-
}
|
|
144
|
-
// Update offset and debug if changed
|
|
145
|
-
if ((newOptions?.offset !== undefined && newOptions.offset !== options?.offset) ||
|
|
146
|
-
(newOptions?.debug !== undefined && newOptions.debug !== options?.debug)) {
|
|
147
|
-
options = { ...options, ...newOptions };
|
|
148
|
-
recreateSentinel();
|
|
149
|
-
}
|
|
150
|
-
},
|
|
151
|
-
destroy() {
|
|
152
|
-
disconnectObserver(intersectionObserver, state);
|
|
153
|
-
// Cleanup ResizeObserver
|
|
154
|
-
if (resizeObserver) {
|
|
155
|
-
resizeObserver.disconnect();
|
|
156
|
-
}
|
|
157
|
-
currentSentinel.remove();
|
|
158
|
-
// Unwrap element (move it out of wrapper)
|
|
159
|
-
const parent = wrapper.parentElement;
|
|
160
|
-
if (parent) {
|
|
161
|
-
wrapper.insertAdjacentElement('beforebegin', element);
|
|
162
|
-
}
|
|
163
|
-
wrapper.remove();
|
|
164
|
-
}
|
|
165
|
-
};
|
|
166
|
-
}
|
package/dist/types.d.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Animation type names available in Rune Scroller
|
|
3
|
-
*/
|
|
4
|
-
export type AnimationType = "fade-in" | "fade-in-up" | "fade-in-down" | "fade-in-left" | "fade-in-right" | "zoom-in" | "zoom-out" | "zoom-in-up" | "zoom-in-left" | "zoom-in-right" | "flip" | "flip-x" | "slide-rotate" | "bounce-in";
|
|
5
|
-
/**
|
|
6
|
-
* Options for the runeScroller action
|
|
7
|
-
* Sentinel-based scroll animation triggering
|
|
8
|
-
*/
|
|
9
|
-
export type RuneScrollerOptions = {
|
|
10
|
-
/**
|
|
11
|
-
* - Animation type to apply
|
|
12
|
-
*/
|
|
13
|
-
animation?: AnimationType | undefined;
|
|
14
|
-
/**
|
|
15
|
-
* - Animation duration in milliseconds
|
|
16
|
-
*/
|
|
17
|
-
duration?: number | undefined;
|
|
18
|
-
/**
|
|
19
|
-
* - Repeat animation on every scroll
|
|
20
|
-
*/
|
|
21
|
-
repeat?: boolean | undefined;
|
|
22
|
-
/**
|
|
23
|
-
* - Show sentinel as visible line for debugging
|
|
24
|
-
*/
|
|
25
|
-
debug?: boolean | undefined;
|
|
26
|
-
/**
|
|
27
|
-
* - Sentinel color for debug mode (hex or CSS color)
|
|
28
|
-
*/
|
|
29
|
-
sentinelColor?: string | undefined;
|
|
30
|
-
/**
|
|
31
|
-
* - Unique identifier for sentinel (auto-generated if not provided)
|
|
32
|
-
*/
|
|
33
|
-
sentinelId?: string | undefined;
|
|
34
|
-
/**
|
|
35
|
-
* - Debug label to show on sentinel (e.g., animation name)
|
|
36
|
-
*/
|
|
37
|
-
debugLabel?: string | undefined;
|
|
38
|
-
/**
|
|
39
|
-
* - Offset of sentinel in pixels (negative = above element)
|
|
40
|
-
*/
|
|
41
|
-
offset?: number | undefined;
|
|
42
|
-
/**
|
|
43
|
-
* - Callback fired when animation becomes visible
|
|
44
|
-
*/
|
|
45
|
-
onVisible?: ((element: HTMLElement) => void) | undefined;
|
|
46
|
-
};
|
|
47
|
-
/**
|
|
48
|
-
* Configuration options for IntersectionObserver
|
|
49
|
-
* Used by useIntersection and useIntersectionOnce composables
|
|
50
|
-
*/
|
|
51
|
-
export type IntersectionOptions = {
|
|
52
|
-
/**
|
|
53
|
-
* - IntersectionObserver threshold
|
|
54
|
-
*/
|
|
55
|
-
threshold?: number | number[] | undefined;
|
|
56
|
-
/**
|
|
57
|
-
* - Custom margin around root element
|
|
58
|
-
*/
|
|
59
|
-
rootMargin?: string | undefined;
|
|
60
|
-
/**
|
|
61
|
-
* - Root element for intersection observation
|
|
62
|
-
*/
|
|
63
|
-
root?: Element | null | undefined;
|
|
64
|
-
};
|
|
65
|
-
/**
|
|
66
|
-
* Return type for useIntersection and useIntersectionOnce composables
|
|
67
|
-
* Provides reactive element reference and visibility state
|
|
68
|
-
*/
|
|
69
|
-
export type UseIntersectionReturn = {
|
|
70
|
-
/**
|
|
71
|
-
* - Reference to the DOM element being observed
|
|
72
|
-
*/
|
|
73
|
-
element: HTMLElement | null;
|
|
74
|
-
/**
|
|
75
|
-
* - Whether the element is currently visible in viewport
|
|
76
|
-
*/
|
|
77
|
-
isVisible: boolean;
|
|
78
|
-
};
|