use-scroll-animate 1.2.0 → 1.5.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/CHANGELOG.md +125 -58
- package/LICENSE +21 -21
- package/README.md +256 -214
- package/README_ja.md +106 -69
- package/README_zh.md +106 -69
- package/dist/index.d.mts +316 -0
- package/dist/index.d.ts +316 -0
- package/dist/index.esm.js +1249 -697
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1263 -708
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1249 -0
- package/dist/index.mjs.map +1 -0
- package/dist/index.umd.js +13 -14
- package/dist/index.umd.js.map +1 -1
- package/dist/types/core.d.ts +41 -7
- package/dist/types/index.d.ts +36 -33
- package/dist/types/presets.d.ts +15 -15
- package/dist/types/react.d.ts +29 -18
- package/dist/types/sequence.d.ts +38 -0
- package/dist/types/stagger.d.ts +25 -0
- package/dist/types/types.d.ts +153 -123
- package/dist/types/vue.d.ts +28 -18
- package/package.json +81 -35
- package/CONTRIBUTING.md +0 -76
- package/examples/react/App.tsx +0 -61
- package/examples/vanilla/index.html +0 -201
- package/rollup.config.js +0 -41
- package/src/core.ts +0 -373
- package/src/index.ts +0 -48
- package/src/presets.ts +0 -148
- package/src/react.ts +0 -175
- package/src/types.ts +0 -161
- package/src/vue.ts +0 -118
- package/tsconfig.json +0 -17
package/src/presets.ts
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* use-scroll-animate - Animation Presets
|
|
3
|
-
* Defines keyframes for all built-in animation presets
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { AnimationPreset, CustomAnimation } from './types';
|
|
7
|
-
|
|
8
|
-
type KeyframeMap = {
|
|
9
|
-
from: Record<string, string | number>;
|
|
10
|
-
to: Record<string, string | number>;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export const PRESETS: Record<AnimationPreset, KeyframeMap> = {
|
|
14
|
-
'fade-in': {
|
|
15
|
-
from: { opacity: 0 },
|
|
16
|
-
to: { opacity: 1 },
|
|
17
|
-
},
|
|
18
|
-
'fade-in-up': {
|
|
19
|
-
from: { opacity: 0, transform: 'translateY(40px)' },
|
|
20
|
-
to: { opacity: 1, transform: 'translateY(0)' },
|
|
21
|
-
},
|
|
22
|
-
'fade-in-down': {
|
|
23
|
-
from: { opacity: 0, transform: 'translateY(-40px)' },
|
|
24
|
-
to: { opacity: 1, transform: 'translateY(0)' },
|
|
25
|
-
},
|
|
26
|
-
'fade-in-left': {
|
|
27
|
-
from: { opacity: 0, transform: 'translateX(-40px)' },
|
|
28
|
-
to: { opacity: 1, transform: 'translateX(0)' },
|
|
29
|
-
},
|
|
30
|
-
'fade-in-right': {
|
|
31
|
-
from: { opacity: 0, transform: 'translateX(40px)' },
|
|
32
|
-
to: { opacity: 1, transform: 'translateX(0)' },
|
|
33
|
-
},
|
|
34
|
-
'zoom-in': {
|
|
35
|
-
from: { opacity: 0, transform: 'scale(0.8)' },
|
|
36
|
-
to: { opacity: 1, transform: 'scale(1)' },
|
|
37
|
-
},
|
|
38
|
-
'zoom-out': {
|
|
39
|
-
from: { opacity: 0, transform: 'scale(1.2)' },
|
|
40
|
-
to: { opacity: 1, transform: 'scale(1)' },
|
|
41
|
-
},
|
|
42
|
-
'flip-x': {
|
|
43
|
-
from: { opacity: 0, transform: 'rotateX(-90deg)' },
|
|
44
|
-
to: { opacity: 1, transform: 'rotateX(0)' },
|
|
45
|
-
},
|
|
46
|
-
'flip-y': {
|
|
47
|
-
from: { opacity: 0, transform: 'rotateY(-90deg)' },
|
|
48
|
-
to: { opacity: 1, transform: 'rotateY(0)' },
|
|
49
|
-
},
|
|
50
|
-
'slide-up': {
|
|
51
|
-
from: { transform: 'translateY(100%)' },
|
|
52
|
-
to: { transform: 'translateY(0)' },
|
|
53
|
-
},
|
|
54
|
-
'slide-down': {
|
|
55
|
-
from: { transform: 'translateY(-100%)' },
|
|
56
|
-
to: { transform: 'translateY(0)' },
|
|
57
|
-
},
|
|
58
|
-
'slide-left': {
|
|
59
|
-
from: { transform: 'translateX(-100%)' },
|
|
60
|
-
to: { transform: 'translateX(0)' },
|
|
61
|
-
},
|
|
62
|
-
'slide-right': {
|
|
63
|
-
from: { transform: 'translateX(100%)' },
|
|
64
|
-
to: { transform: 'translateX(0)' },
|
|
65
|
-
},
|
|
66
|
-
'bounce': {
|
|
67
|
-
from: { opacity: 0, transform: 'translateY(-60px)' },
|
|
68
|
-
to: { opacity: 1, transform: 'translateY(0)' },
|
|
69
|
-
},
|
|
70
|
-
'rotate-in': {
|
|
71
|
-
from: { opacity: 0, transform: 'rotate(-180deg) scale(0.5)' },
|
|
72
|
-
to: { opacity: 1, transform: 'rotate(0deg) scale(1)' },
|
|
73
|
-
},
|
|
74
|
-
'blur-in': {
|
|
75
|
-
from: { opacity: 0, filter: 'blur(12px)' },
|
|
76
|
-
to: { opacity: 1, filter: 'blur(0px)' },
|
|
77
|
-
},
|
|
78
|
-
'skew-in': {
|
|
79
|
-
from: { opacity: 0, transform: 'skewX(20deg) translateX(30px)' },
|
|
80
|
-
to: { opacity: 1, transform: 'skewX(0deg) translateX(0)' },
|
|
81
|
-
},
|
|
82
|
-
'scale-x': {
|
|
83
|
-
from: { transform: 'scaleX(0)' },
|
|
84
|
-
to: { transform: 'scaleX(1)' },
|
|
85
|
-
},
|
|
86
|
-
'scale-y': {
|
|
87
|
-
from: { transform: 'scaleY(0)' },
|
|
88
|
-
to: { transform: 'scaleY(1)' },
|
|
89
|
-
},
|
|
90
|
-
'shimmer': {
|
|
91
|
-
from: { opacity: 0.5, filter: 'brightness(1)' },
|
|
92
|
-
to: { opacity: 1, filter: 'brightness(1.5)' },
|
|
93
|
-
},
|
|
94
|
-
'pulse': {
|
|
95
|
-
from: { transform: 'scale(1)' },
|
|
96
|
-
to: { transform: 'scale(1.05)' },
|
|
97
|
-
},
|
|
98
|
-
'swing': {
|
|
99
|
-
from: { transform: 'rotate(-10deg)' },
|
|
100
|
-
to: { transform: 'rotate(10deg)' },
|
|
101
|
-
},
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
export function resolvePreset(animation: AnimationPreset | AnimationPreset[] | CustomAnimation): KeyframeMap {
|
|
105
|
-
if (typeof animation === 'string') {
|
|
106
|
-
return PRESETS[animation] ?? PRESETS['fade-in-up'];
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (Array.isArray(animation)) {
|
|
110
|
-
const combined: KeyframeMap = { from: {}, to: {} };
|
|
111
|
-
animation.forEach(name => {
|
|
112
|
-
const preset = PRESETS[name];
|
|
113
|
-
if (preset) {
|
|
114
|
-
Object.entries(preset.from).forEach(([key, val]) => {
|
|
115
|
-
if (key === 'transform' && combined.from[key]) {
|
|
116
|
-
combined.from[key] = `${combined.from[key]} ${val}`;
|
|
117
|
-
} else {
|
|
118
|
-
combined.from[key] = val;
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
Object.entries(preset.to).forEach(([key, val]) => {
|
|
122
|
-
if (key === 'transform' && combined.to[key]) {
|
|
123
|
-
combined.to[key] = `${combined.to[key]} ${val}`;
|
|
124
|
-
} else {
|
|
125
|
-
combined.to[key] = val;
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
return combined;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return animation;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/** Easing to CSS cubic-bezier mapping */
|
|
137
|
-
export const EASING_MAP: Record<string, string> = {
|
|
138
|
-
linear: 'linear',
|
|
139
|
-
ease: 'ease',
|
|
140
|
-
'ease-in': 'ease-in',
|
|
141
|
-
'ease-out': 'ease-out',
|
|
142
|
-
'ease-in-out': 'ease-in-out',
|
|
143
|
-
spring: 'cubic-bezier(0.34, 1.56, 0.64, 1)',
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
export function resolveEasing(easing: string): string {
|
|
147
|
-
return EASING_MAP[easing] ?? easing;
|
|
148
|
-
}
|
package/src/react.ts
DELETED
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* use-scroll-animate - React Integration
|
|
3
|
-
* Provides useScrollAnimate and useScrollRef hooks for React applications.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { AnimateOptions } from './types';
|
|
7
|
-
import { resolvePreset, resolveEasing } from './presets';
|
|
8
|
-
|
|
9
|
-
type ReactRef<T> = { current: T | null };
|
|
10
|
-
|
|
11
|
-
export function createReactHooks(React: {
|
|
12
|
-
useRef: <T>(initial: T | null) => ReactRef<T>;
|
|
13
|
-
useEffect: (effect: () => (() => void) | void, deps?: unknown[]) => void;
|
|
14
|
-
}) {
|
|
15
|
-
function useScrollAnimate(options: AnimateOptions = {}) {
|
|
16
|
-
const ref = React.useRef<Element>(null);
|
|
17
|
-
|
|
18
|
-
React.useEffect(() => {
|
|
19
|
-
const el = ref.current;
|
|
20
|
-
if (!el) return;
|
|
21
|
-
|
|
22
|
-
const {
|
|
23
|
-
animation = 'fade-in-up',
|
|
24
|
-
duration = 600,
|
|
25
|
-
delay = 0,
|
|
26
|
-
easing = 'ease',
|
|
27
|
-
threshold = 0.1,
|
|
28
|
-
rootMargin = '0px',
|
|
29
|
-
repeat = false,
|
|
30
|
-
parallax = {},
|
|
31
|
-
onStart,
|
|
32
|
-
onComplete,
|
|
33
|
-
onEnter,
|
|
34
|
-
onLeave,
|
|
35
|
-
onProgress,
|
|
36
|
-
} = options;
|
|
37
|
-
|
|
38
|
-
const preset = resolvePreset(animation);
|
|
39
|
-
const easingValue = resolveEasing(easing);
|
|
40
|
-
let animated = false;
|
|
41
|
-
|
|
42
|
-
(el as HTMLElement).style.opacity = '0';
|
|
43
|
-
|
|
44
|
-
const observer = new IntersectionObserver(
|
|
45
|
-
(entries) => {
|
|
46
|
-
entries.forEach((entry) => {
|
|
47
|
-
if (entry.isIntersecting) {
|
|
48
|
-
onEnter?.(el);
|
|
49
|
-
if (!animated || repeat) {
|
|
50
|
-
const keyframes: Keyframe[] = [
|
|
51
|
-
preset.from as Keyframe,
|
|
52
|
-
preset.to as Keyframe,
|
|
53
|
-
];
|
|
54
|
-
const anim = el.animate(keyframes, {
|
|
55
|
-
duration,
|
|
56
|
-
delay,
|
|
57
|
-
easing: easingValue,
|
|
58
|
-
fill: 'both',
|
|
59
|
-
});
|
|
60
|
-
onStart?.(el);
|
|
61
|
-
anim.onfinish = () => onComplete?.(el);
|
|
62
|
-
animated = true;
|
|
63
|
-
if (!repeat && !Object.keys(parallax).length && !onProgress) {
|
|
64
|
-
observer.unobserve(el);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
} else {
|
|
68
|
-
onLeave?.(el);
|
|
69
|
-
if (repeat && animated) {
|
|
70
|
-
(el as HTMLElement).style.opacity = '0';
|
|
71
|
-
animated = false;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
},
|
|
76
|
-
{ threshold: typeof threshold === 'number' ? threshold : threshold[0], rootMargin }
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
let progressObserver: IntersectionObserver | null = null;
|
|
80
|
-
if (Object.keys(parallax).length > 0 || onProgress) {
|
|
81
|
-
const thresholds = [];
|
|
82
|
-
for (let i = 0; i <= 100; i++) thresholds.push(i / 100);
|
|
83
|
-
|
|
84
|
-
progressObserver = new IntersectionObserver(
|
|
85
|
-
(entries) => {
|
|
86
|
-
entries.forEach((entry) => {
|
|
87
|
-
const progress = entry.intersectionRatio;
|
|
88
|
-
onProgress?.(el, progress);
|
|
89
|
-
|
|
90
|
-
if (Object.keys(parallax).length > 0) {
|
|
91
|
-
const { x = 0, y = 0, rotate = 0, scale = 1, speed = 1 } = parallax;
|
|
92
|
-
const p = (progress - 0.5) * 2 * speed;
|
|
93
|
-
let transform = '';
|
|
94
|
-
if (x) transform += ` translateX(${typeof x === 'number' ? x * p + 'px' : 'calc(' + x + ' * ' + p + ')'})`;
|
|
95
|
-
if (y) transform += ` translateY(${typeof y === 'number' ? y * p + 'px' : 'calc(' + y + ' * ' + p + ')'})`;
|
|
96
|
-
if (rotate) transform += ` rotate(${rotate * p}deg)`;
|
|
97
|
-
if (scale !== 1) transform += ` scale(${1 + (scale - 1) * p})`;
|
|
98
|
-
(el as HTMLElement).style.transform = transform;
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
},
|
|
102
|
-
{ threshold: thresholds, rootMargin }
|
|
103
|
-
);
|
|
104
|
-
progressObserver.observe(el);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
observer.observe(el);
|
|
108
|
-
return () => {
|
|
109
|
-
observer.disconnect();
|
|
110
|
-
progressObserver?.disconnect();
|
|
111
|
-
};
|
|
112
|
-
}, []);
|
|
113
|
-
|
|
114
|
-
return ref;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function useScrollStagger(options: AnimateOptions & { stagger?: number } = {}) {
|
|
118
|
-
const ref = React.useRef<Element>(null);
|
|
119
|
-
|
|
120
|
-
React.useEffect(() => {
|
|
121
|
-
const container = ref.current;
|
|
122
|
-
if (!container) return;
|
|
123
|
-
|
|
124
|
-
const {
|
|
125
|
-
animation = 'fade-in-up',
|
|
126
|
-
duration = 600,
|
|
127
|
-
delay = 0,
|
|
128
|
-
easing = 'ease',
|
|
129
|
-
threshold = 0.1,
|
|
130
|
-
rootMargin = '0px',
|
|
131
|
-
stagger = 80,
|
|
132
|
-
} = options;
|
|
133
|
-
|
|
134
|
-
const children = Array.from(container.children) as HTMLElement[];
|
|
135
|
-
const preset = resolvePreset(animation);
|
|
136
|
-
const easingValue = resolveEasing(easing);
|
|
137
|
-
|
|
138
|
-
children.forEach((child) => {
|
|
139
|
-
child.style.opacity = '0';
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
let animated = false;
|
|
143
|
-
|
|
144
|
-
const observer = new IntersectionObserver(
|
|
145
|
-
(entries) => {
|
|
146
|
-
entries.forEach((entry) => {
|
|
147
|
-
if (entry.isIntersecting && !animated) {
|
|
148
|
-
animated = true;
|
|
149
|
-
children.forEach((child, i) => {
|
|
150
|
-
child.animate(
|
|
151
|
-
[preset.from as Keyframe, preset.to as Keyframe],
|
|
152
|
-
{
|
|
153
|
-
duration,
|
|
154
|
-
delay: delay + i * stagger,
|
|
155
|
-
easing: easingValue,
|
|
156
|
-
fill: 'both',
|
|
157
|
-
}
|
|
158
|
-
);
|
|
159
|
-
});
|
|
160
|
-
observer.unobserve(container);
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
},
|
|
164
|
-
{ threshold: typeof threshold === 'number' ? threshold : threshold[0], rootMargin }
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
observer.observe(container);
|
|
168
|
-
return () => observer.disconnect();
|
|
169
|
-
}, []);
|
|
170
|
-
|
|
171
|
-
return ref;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return { useScrollAnimate, useScrollStagger };
|
|
175
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* use-scroll-animate - Core Type Definitions
|
|
3
|
-
* A lightweight, high-performance scroll animation library
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/** Built-in animation presets */
|
|
7
|
-
export type AnimationPreset =
|
|
8
|
-
| 'fade-in'
|
|
9
|
-
| 'fade-in-up'
|
|
10
|
-
| 'fade-in-down'
|
|
11
|
-
| 'fade-in-left'
|
|
12
|
-
| 'fade-in-right'
|
|
13
|
-
| 'zoom-in'
|
|
14
|
-
| 'zoom-out'
|
|
15
|
-
| 'flip-x'
|
|
16
|
-
| 'flip-y'
|
|
17
|
-
| 'slide-up'
|
|
18
|
-
| 'slide-down'
|
|
19
|
-
| 'slide-left'
|
|
20
|
-
| 'slide-right'
|
|
21
|
-
| 'bounce'
|
|
22
|
-
| 'rotate-in'
|
|
23
|
-
| 'blur-in'
|
|
24
|
-
| 'skew-in'
|
|
25
|
-
| 'scale-x'
|
|
26
|
-
| 'scale-y'
|
|
27
|
-
| 'shimmer'
|
|
28
|
-
| 'pulse'
|
|
29
|
-
| 'swing';
|
|
30
|
-
|
|
31
|
-
/** Easing function types */
|
|
32
|
-
export type EasingType =
|
|
33
|
-
| 'linear'
|
|
34
|
-
| 'ease'
|
|
35
|
-
| 'ease-in'
|
|
36
|
-
| 'ease-out'
|
|
37
|
-
| 'ease-in-out'
|
|
38
|
-
| 'spring'
|
|
39
|
-
| string;
|
|
40
|
-
|
|
41
|
-
/** Keyframe definition for custom animations */
|
|
42
|
-
export interface AnimationKeyframe {
|
|
43
|
-
[property: string]: string | number;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** Custom animation definition */
|
|
47
|
-
export interface CustomAnimation {
|
|
48
|
-
from: AnimationKeyframe;
|
|
49
|
-
to: AnimationKeyframe;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** Parallax configuration */
|
|
53
|
-
export interface ParallaxOptions {
|
|
54
|
-
/** Movement on X axis (e.g., '100px', '20%') */
|
|
55
|
-
x?: string | number;
|
|
56
|
-
/** Movement on Y axis (e.g., '100px', '20%') */
|
|
57
|
-
y?: string | number;
|
|
58
|
-
/** Rotation in degrees */
|
|
59
|
-
rotate?: number;
|
|
60
|
-
/** Scale factor */
|
|
61
|
-
scale?: number;
|
|
62
|
-
/** Speed multiplier (default: 1) */
|
|
63
|
-
speed?: number;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** Per-element animation options */
|
|
67
|
-
export interface AnimateOptions {
|
|
68
|
-
/** Animation preset name, array of presets, or custom animation object */
|
|
69
|
-
animation?: AnimationPreset | AnimationPreset[] | CustomAnimation;
|
|
70
|
-
/** Duration in milliseconds (default: 600) */
|
|
71
|
-
duration?: number;
|
|
72
|
-
/** Delay in milliseconds (default: 0) */
|
|
73
|
-
delay?: number;
|
|
74
|
-
/** Easing function (default: 'ease') */
|
|
75
|
-
easing?: EasingType;
|
|
76
|
-
/** Intersection threshold 0-1 (default: 0.1) */
|
|
77
|
-
threshold?: number | number[];
|
|
78
|
-
/** Root margin for IntersectionObserver (default: '0px') */
|
|
79
|
-
rootMargin?: string;
|
|
80
|
-
/** Whether to replay animation each time element enters viewport (default: false) */
|
|
81
|
-
repeat?: boolean;
|
|
82
|
-
/** Whether to trigger animation only once (default: true if repeat is false) */
|
|
83
|
-
once?: boolean;
|
|
84
|
-
/** Offset in pixels from the viewport edge to trigger animation (default: 0) */
|
|
85
|
-
offset?: number;
|
|
86
|
-
/** Stagger delay for child elements in ms (default: 0) */
|
|
87
|
-
stagger?: number;
|
|
88
|
-
/** Parallax effect configuration */
|
|
89
|
-
parallax?: ParallaxOptions;
|
|
90
|
-
/** Callback fired when animation starts */
|
|
91
|
-
onStart?: (element: Element) => void;
|
|
92
|
-
/** Callback fired when animation completes */
|
|
93
|
-
onComplete?: (element: Element) => void;
|
|
94
|
-
/** Callback fired when element enters viewport */
|
|
95
|
-
onEnter?: (element: Element) => void;
|
|
96
|
-
/** Callback fired when element leaves viewport */
|
|
97
|
-
onLeave?: (element: Element) => void;
|
|
98
|
-
/** Callback fired with scroll progress (0 to 1) */
|
|
99
|
-
onProgress?: (element: Element, progress: number) => void;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/** Global configuration for ScrollAnimate instance */
|
|
103
|
-
export interface ScrollAnimateConfig {
|
|
104
|
-
/** Default animation preset (default: 'fade-in-up') */
|
|
105
|
-
defaultAnimation?: AnimationPreset | AnimationPreset[] | CustomAnimation;
|
|
106
|
-
/** Default duration in ms (default: 600) */
|
|
107
|
-
defaultDuration?: number;
|
|
108
|
-
/** Default delay in ms (default: 0) */
|
|
109
|
-
defaultDelay?: number;
|
|
110
|
-
/** Default easing (default: 'ease') */
|
|
111
|
-
defaultEasing?: EasingType;
|
|
112
|
-
/** Default threshold (default: 0.1) */
|
|
113
|
-
defaultThreshold?: number | number[];
|
|
114
|
-
/** Default root margin (default: '0px') */
|
|
115
|
-
defaultRootMargin?: string;
|
|
116
|
-
/** Whether animations replay by default (default: false) */
|
|
117
|
-
defaultRepeat?: boolean;
|
|
118
|
-
/** Default once setting (default: true) */
|
|
119
|
-
defaultOnce?: boolean;
|
|
120
|
-
/** Default offset in pixels (default: 0) */
|
|
121
|
-
defaultOffset?: number;
|
|
122
|
-
/** CSS class added before animation (default: 'sa-hidden') */
|
|
123
|
-
hiddenClass?: string;
|
|
124
|
-
/** CSS class added when element is visible (default: 'sa-visible') */
|
|
125
|
-
visibleClass?: string;
|
|
126
|
-
/** Whether to use CSS class-based animation instead of Web Animations API */
|
|
127
|
-
useClassNames?: boolean;
|
|
128
|
-
/** Disable all animations (useful for reduced-motion preference) */
|
|
129
|
-
disabled?: boolean;
|
|
130
|
-
/** Custom IntersectionObserver root element */
|
|
131
|
-
root?: Element | null;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/** Registered element entry */
|
|
135
|
-
export interface AnimatedElement {
|
|
136
|
-
element: Element;
|
|
137
|
-
options: Required<AnimateOptions>;
|
|
138
|
-
observer: IntersectionObserver;
|
|
139
|
-
animated: boolean;
|
|
140
|
-
progressObserver?: IntersectionObserver;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/** ScrollAnimate public API */
|
|
144
|
-
export interface ScrollAnimateInstance {
|
|
145
|
-
/** Observe a single element or CSS selector */
|
|
146
|
-
observe(target: string | Element | NodeList | Element[], options?: AnimateOptions): void;
|
|
147
|
-
/** Stop observing a single element or CSS selector */
|
|
148
|
-
unobserve(target: string | Element | NodeList | Element[]): void;
|
|
149
|
-
/** Observe all elements matching the data-sa attribute */
|
|
150
|
-
init(rootElement?: Element | Document): void;
|
|
151
|
-
/** Destroy the instance and clean up all observers */
|
|
152
|
-
destroy(): void;
|
|
153
|
-
/** Refresh all observers (useful after DOM changes) */
|
|
154
|
-
refresh(): void;
|
|
155
|
-
/** Manually trigger animation on an element */
|
|
156
|
-
animate(target: string | Element, options?: AnimateOptions): void;
|
|
157
|
-
/** Get all currently observed elements */
|
|
158
|
-
getObservedElements(): AnimatedElement[];
|
|
159
|
-
/** Update global configuration */
|
|
160
|
-
configure(config: Partial<ScrollAnimateConfig>): void;
|
|
161
|
-
}
|
package/src/vue.ts
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* use-scroll-animate - Vue 3 Integration
|
|
3
|
-
* Provides useScrollAnimate composable for Vue 3 applications.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { AnimateOptions } from './types';
|
|
7
|
-
import { resolvePreset, resolveEasing } from './presets';
|
|
8
|
-
|
|
9
|
-
export function createVueComposables(Vue: {
|
|
10
|
-
ref: <T>(value: T | null) => { value: T | null };
|
|
11
|
-
onMounted: (fn: () => void) => void;
|
|
12
|
-
onUnmounted: (fn: () => void) => void;
|
|
13
|
-
}) {
|
|
14
|
-
function useScrollAnimate(options: AnimateOptions = {}) {
|
|
15
|
-
const animateRef = Vue.ref<Element>(null);
|
|
16
|
-
let observer: IntersectionObserver | null = null;
|
|
17
|
-
let progressObserver: IntersectionObserver | null = null;
|
|
18
|
-
|
|
19
|
-
Vue.onMounted(() => {
|
|
20
|
-
const el = animateRef.value;
|
|
21
|
-
if (!el) return;
|
|
22
|
-
|
|
23
|
-
const {
|
|
24
|
-
animation = 'fade-in-up',
|
|
25
|
-
duration = 600,
|
|
26
|
-
delay = 0,
|
|
27
|
-
easing = 'ease',
|
|
28
|
-
threshold = 0.1,
|
|
29
|
-
rootMargin = '0px',
|
|
30
|
-
repeat = false,
|
|
31
|
-
parallax = {},
|
|
32
|
-
onStart,
|
|
33
|
-
onComplete,
|
|
34
|
-
onEnter,
|
|
35
|
-
onLeave,
|
|
36
|
-
onProgress,
|
|
37
|
-
} = options;
|
|
38
|
-
|
|
39
|
-
const preset = resolvePreset(animation);
|
|
40
|
-
const easingValue = resolveEasing(easing);
|
|
41
|
-
let animated = false;
|
|
42
|
-
|
|
43
|
-
(el as HTMLElement).style.opacity = '0';
|
|
44
|
-
|
|
45
|
-
observer = new IntersectionObserver(
|
|
46
|
-
(entries) => {
|
|
47
|
-
entries.forEach((entry) => {
|
|
48
|
-
if (entry.isIntersecting) {
|
|
49
|
-
onEnter?.(el);
|
|
50
|
-
if (!animated || repeat) {
|
|
51
|
-
const anim = el.animate(
|
|
52
|
-
[preset.from as Keyframe, preset.to as Keyframe],
|
|
53
|
-
{
|
|
54
|
-
duration,
|
|
55
|
-
delay,
|
|
56
|
-
easing: easingValue,
|
|
57
|
-
fill: 'both',
|
|
58
|
-
}
|
|
59
|
-
);
|
|
60
|
-
onStart?.(el);
|
|
61
|
-
anim.onfinish = () => onComplete?.(el);
|
|
62
|
-
animated = true;
|
|
63
|
-
if (!repeat && !Object.keys(parallax).length && !onProgress) {
|
|
64
|
-
observer?.unobserve(el);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
} else {
|
|
68
|
-
onLeave?.(el);
|
|
69
|
-
if (repeat && animated) {
|
|
70
|
-
(el as HTMLElement).style.opacity = '0';
|
|
71
|
-
animated = false;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
},
|
|
76
|
-
{ threshold: typeof threshold === 'number' ? threshold : threshold[0], rootMargin }
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
if (Object.keys(parallax).length > 0 || onProgress) {
|
|
80
|
-
const thresholds = [];
|
|
81
|
-
for (let i = 0; i <= 100; i++) thresholds.push(i / 100);
|
|
82
|
-
|
|
83
|
-
progressObserver = new IntersectionObserver(
|
|
84
|
-
(entries) => {
|
|
85
|
-
entries.forEach((entry) => {
|
|
86
|
-
const progress = entry.intersectionRatio;
|
|
87
|
-
onProgress?.(el, progress);
|
|
88
|
-
|
|
89
|
-
if (Object.keys(parallax).length > 0) {
|
|
90
|
-
const { x = 0, y = 0, rotate = 0, scale = 1, speed = 1 } = parallax;
|
|
91
|
-
const p = (progress - 0.5) * 2 * speed;
|
|
92
|
-
let transform = '';
|
|
93
|
-
if (x) transform += ` translateX(${typeof x === 'number' ? x * p + 'px' : 'calc(' + x + ' * ' + p + ')'})`;
|
|
94
|
-
if (y) transform += ` translateY(${typeof y === 'number' ? y * p + 'px' : 'calc(' + y + ' * ' + p + ')'})`;
|
|
95
|
-
if (rotate) transform += ` rotate(${rotate * p}deg)`;
|
|
96
|
-
if (scale !== 1) transform += ` scale(${1 + (scale - 1) * p})`;
|
|
97
|
-
(el as HTMLElement).style.transform = transform;
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
},
|
|
101
|
-
{ threshold: thresholds, rootMargin }
|
|
102
|
-
);
|
|
103
|
-
progressObserver.observe(el);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
observer.observe(el);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
Vue.onUnmounted(() => {
|
|
110
|
-
observer?.disconnect();
|
|
111
|
-
progressObserver?.disconnect();
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
return { animateRef };
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return { useScrollAnimate };
|
|
118
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "es2018",
|
|
4
|
-
"module": "esnext",
|
|
5
|
-
"lib": ["dom", "esnext"],
|
|
6
|
-
"declaration": true,
|
|
7
|
-
"declarationDir": "./dist/types",
|
|
8
|
-
"outDir": "./dist",
|
|
9
|
-
"strict": true,
|
|
10
|
-
"esModuleInterop": true,
|
|
11
|
-
"skipLibCheck": true,
|
|
12
|
-
"forceConsistentCasingInFileNames": true,
|
|
13
|
-
"moduleResolution": "node"
|
|
14
|
-
},
|
|
15
|
-
"include": ["src/**/*"],
|
|
16
|
-
"exclude": ["node_modules", "dist"]
|
|
17
|
-
}
|