rune-scroller 0.1.0 → 0.1.1
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/dist/animate.svelte.js +34 -14
- package/dist/dom-utils.svelte.d.ts +20 -0
- package/dist/dom-utils.svelte.js +33 -0
- package/dist/runeScroller.svelte.js +12 -20
- package/package.json +1 -1
package/dist/animate.svelte.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { calculateRootMargin } from './animations';
|
|
2
|
+
import { setCSSVariables, setupAnimationElement } from './dom-utils.svelte';
|
|
2
3
|
/**
|
|
3
4
|
* Svelte action for scroll animations
|
|
4
5
|
* Triggers animation once when element enters viewport
|
|
@@ -11,17 +12,15 @@ import { calculateRootMargin } from './animations';
|
|
|
11
12
|
* ```
|
|
12
13
|
*/
|
|
13
14
|
export const animate = (node, options = {}) => {
|
|
14
|
-
|
|
15
|
+
let { animation = 'fade-in', duration = 800, delay = 0, offset, threshold = 0, rootMargin } = options;
|
|
15
16
|
// Calculate rootMargin from offset (0-100%)
|
|
16
|
-
|
|
17
|
-
//
|
|
18
|
-
node
|
|
19
|
-
node
|
|
20
|
-
// Add base animation class and data attribute
|
|
21
|
-
node.classList.add('scroll-animate');
|
|
22
|
-
node.setAttribute('data-animation', animation);
|
|
17
|
+
let finalRootMargin = calculateRootMargin(offset, rootMargin);
|
|
18
|
+
// Setup animation with utilities
|
|
19
|
+
setupAnimationElement(node, animation);
|
|
20
|
+
setCSSVariables(node, duration, delay);
|
|
23
21
|
// Track if animation has been triggered
|
|
24
22
|
let animated = false;
|
|
23
|
+
let observerConnected = true;
|
|
25
24
|
// Create IntersectionObserver for one-time animation
|
|
26
25
|
const observer = new IntersectionObserver((entries) => {
|
|
27
26
|
entries.forEach((entry) => {
|
|
@@ -31,6 +30,7 @@ export const animate = (node, options = {}) => {
|
|
|
31
30
|
animated = true;
|
|
32
31
|
// Stop observing after animation triggers
|
|
33
32
|
observer.unobserve(node);
|
|
33
|
+
observerConnected = false;
|
|
34
34
|
}
|
|
35
35
|
});
|
|
36
36
|
}, {
|
|
@@ -40,20 +40,40 @@ export const animate = (node, options = {}) => {
|
|
|
40
40
|
observer.observe(node);
|
|
41
41
|
return {
|
|
42
42
|
update(newOptions) {
|
|
43
|
-
const { duration: newDuration
|
|
43
|
+
const { duration: newDuration, delay: newDelay, animation: newAnimation, offset: newOffset, threshold: newThreshold, rootMargin: newRootMargin } = newOptions;
|
|
44
44
|
// Update CSS properties
|
|
45
|
-
if (newDuration !==
|
|
46
|
-
|
|
45
|
+
if (newDuration !== undefined) {
|
|
46
|
+
duration = newDuration;
|
|
47
|
+
setCSSVariables(node, duration, newDelay ?? delay);
|
|
47
48
|
}
|
|
48
|
-
if (newDelay !== delay) {
|
|
49
|
-
|
|
49
|
+
if (newDelay !== undefined && newDelay !== delay) {
|
|
50
|
+
delay = newDelay;
|
|
51
|
+
setCSSVariables(node, duration, delay);
|
|
50
52
|
}
|
|
51
53
|
if (newAnimation && newAnimation !== animation) {
|
|
54
|
+
animation = newAnimation;
|
|
52
55
|
node.setAttribute('data-animation', newAnimation);
|
|
53
56
|
}
|
|
57
|
+
// Recreate observer if threshold or rootMargin changed
|
|
58
|
+
if (newThreshold !== undefined || newOffset !== undefined || newRootMargin !== undefined) {
|
|
59
|
+
if (observerConnected) {
|
|
60
|
+
observer.disconnect();
|
|
61
|
+
observerConnected = false;
|
|
62
|
+
}
|
|
63
|
+
threshold = newThreshold ?? threshold;
|
|
64
|
+
offset = newOffset ?? offset;
|
|
65
|
+
rootMargin = newRootMargin ?? rootMargin;
|
|
66
|
+
finalRootMargin = calculateRootMargin(offset, rootMargin);
|
|
67
|
+
if (!animated) {
|
|
68
|
+
observer.observe(node);
|
|
69
|
+
observerConnected = true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
54
72
|
},
|
|
55
73
|
destroy() {
|
|
56
|
-
|
|
74
|
+
if (observerConnected) {
|
|
75
|
+
observer.disconnect();
|
|
76
|
+
}
|
|
57
77
|
}
|
|
58
78
|
};
|
|
59
79
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AnimationType } from './animations';
|
|
2
|
+
/**
|
|
3
|
+
* Set CSS custom properties on an element
|
|
4
|
+
* @param element - Target DOM element
|
|
5
|
+
* @param duration - Animation duration in milliseconds
|
|
6
|
+
* @param delay - Animation delay in milliseconds
|
|
7
|
+
*/
|
|
8
|
+
export declare function setCSSVariables(element: HTMLElement, duration?: number, delay?: number): void;
|
|
9
|
+
/**
|
|
10
|
+
* Setup animation element with required classes and attributes
|
|
11
|
+
* @param element - Target DOM element
|
|
12
|
+
* @param animation - Animation type to apply
|
|
13
|
+
*/
|
|
14
|
+
export declare function setupAnimationElement(element: HTMLElement, animation: AnimationType): void;
|
|
15
|
+
/**
|
|
16
|
+
* Create and inject invisible sentinel element for observer-based triggering
|
|
17
|
+
* @param element - Reference element (sentinel will be placed after it)
|
|
18
|
+
* @returns The created sentinel element
|
|
19
|
+
*/
|
|
20
|
+
export declare function createSentinel(element: HTMLElement): HTMLElement;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Set CSS custom properties on an element
|
|
3
|
+
* @param element - Target DOM element
|
|
4
|
+
* @param duration - Animation duration in milliseconds
|
|
5
|
+
* @param delay - Animation delay in milliseconds
|
|
6
|
+
*/
|
|
7
|
+
export function setCSSVariables(element, duration, delay = 0) {
|
|
8
|
+
if (duration !== undefined) {
|
|
9
|
+
element.style.setProperty('--duration', `${duration}ms`);
|
|
10
|
+
}
|
|
11
|
+
element.style.setProperty('--delay', `${delay}ms`);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Setup animation element with required classes and attributes
|
|
15
|
+
* @param element - Target DOM element
|
|
16
|
+
* @param animation - Animation type to apply
|
|
17
|
+
*/
|
|
18
|
+
export function setupAnimationElement(element, animation) {
|
|
19
|
+
element.classList.add('scroll-animate');
|
|
20
|
+
element.setAttribute('data-animation', animation);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Create and inject invisible sentinel element for observer-based triggering
|
|
24
|
+
* @param element - Reference element (sentinel will be placed after it)
|
|
25
|
+
* @returns The created sentinel element
|
|
26
|
+
*/
|
|
27
|
+
export function createSentinel(element) {
|
|
28
|
+
const sentinel = document.createElement('div');
|
|
29
|
+
// Use cssText for efficient single-statement styling
|
|
30
|
+
sentinel.style.cssText = 'height:20px;margin-top:0.5rem;visibility:hidden';
|
|
31
|
+
element.parentNode?.insertBefore(sentinel, element.nextSibling);
|
|
32
|
+
return sentinel;
|
|
33
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { setCSSVariables, setupAnimationElement, createSentinel } from './dom-utils.svelte';
|
|
1
2
|
/**
|
|
2
3
|
* Action pour animer un élément au scroll avec un sentinel invisible juste en dessous
|
|
3
4
|
* @param element - L'élément à animer
|
|
@@ -18,27 +19,15 @@
|
|
|
18
19
|
* ```
|
|
19
20
|
*/
|
|
20
21
|
export function runeScroller(element, options) {
|
|
21
|
-
// Setup animation classes et variables CSS
|
|
22
|
+
// Setup animation classes et variables CSS
|
|
22
23
|
if (options?.animation || options?.duration) {
|
|
23
|
-
element.
|
|
24
|
-
|
|
25
|
-
element.setAttribute('data-animation', options.animation);
|
|
26
|
-
}
|
|
27
|
-
if (options.duration) {
|
|
28
|
-
element.style.setProperty('--duration', `${options.duration}ms`);
|
|
29
|
-
}
|
|
30
|
-
element.style.setProperty('--delay', '0ms');
|
|
24
|
+
setupAnimationElement(element, options.animation);
|
|
25
|
+
setCSSVariables(element, options.duration);
|
|
31
26
|
}
|
|
32
27
|
// Créer le sentinel invisible juste en dessous
|
|
33
|
-
const sentinel =
|
|
34
|
-
sentinel
|
|
35
|
-
|
|
36
|
-
sentinel.style.padding = '0';
|
|
37
|
-
sentinel.style.marginTop = '0.5rem';
|
|
38
|
-
sentinel.style.visibility = 'hidden';
|
|
39
|
-
// Insérer le sentinel après l'élément
|
|
40
|
-
element.parentNode?.insertBefore(sentinel, element.nextSibling);
|
|
41
|
-
// Observer le sentinel
|
|
28
|
+
const sentinel = createSentinel(element);
|
|
29
|
+
// Observer le sentinel avec cleanup tracking
|
|
30
|
+
let observerConnected = true;
|
|
42
31
|
const observer = new IntersectionObserver((entries) => {
|
|
43
32
|
const isIntersecting = entries[0].isIntersecting;
|
|
44
33
|
if (isIntersecting) {
|
|
@@ -47,6 +36,7 @@ export function runeScroller(element, options) {
|
|
|
47
36
|
// Déconnecter si pas en mode repeat
|
|
48
37
|
if (!options?.repeat) {
|
|
49
38
|
observer.disconnect();
|
|
39
|
+
observerConnected = false;
|
|
50
40
|
}
|
|
51
41
|
}
|
|
52
42
|
else if (options?.repeat) {
|
|
@@ -61,7 +51,7 @@ export function runeScroller(element, options) {
|
|
|
61
51
|
element.setAttribute('data-animation', newOptions.animation);
|
|
62
52
|
}
|
|
63
53
|
if (newOptions?.duration) {
|
|
64
|
-
element
|
|
54
|
+
setCSSVariables(element, newOptions.duration);
|
|
65
55
|
}
|
|
66
56
|
// Update repeat option
|
|
67
57
|
if (newOptions?.repeat !== undefined && newOptions.repeat !== options?.repeat) {
|
|
@@ -69,7 +59,9 @@ export function runeScroller(element, options) {
|
|
|
69
59
|
}
|
|
70
60
|
},
|
|
71
61
|
destroy() {
|
|
72
|
-
|
|
62
|
+
if (observerConnected) {
|
|
63
|
+
observer.disconnect();
|
|
64
|
+
}
|
|
73
65
|
sentinel.remove();
|
|
74
66
|
}
|
|
75
67
|
};
|