use-scroll-animate 1.2.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 +58 -0
- package/CONTRIBUTING.md +76 -0
- package/LICENSE +21 -0
- package/README.md +214 -0
- package/README_ja.md +69 -0
- package/README_zh.md +69 -0
- package/dist/index.esm.js +697 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +708 -0
- package/dist/index.js.map +1 -0
- package/dist/index.umd.js +14 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/types/core.d.ts +7 -0
- package/dist/types/index.d.ts +33 -0
- package/dist/types/presets.d.ts +15 -0
- package/dist/types/react.d.ts +18 -0
- package/dist/types/types.d.ts +123 -0
- package/dist/types/vue.d.ts +18 -0
- package/examples/react/App.tsx +61 -0
- package/examples/vanilla/index.html +201 -0
- package/package.json +35 -0
- package/rollup.config.js +41 -0
- package/src/core.ts +373 -0
- package/src/index.ts +48 -0
- package/src/presets.ts +148 -0
- package/src/react.ts +175 -0
- package/src/types.ts +161 -0
- package/src/vue.ts +118 -0
- package/tsconfig.json +17 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,708 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* use-scroll-animate - Animation Presets
|
|
7
|
+
* Defines keyframes for all built-in animation presets
|
|
8
|
+
*/
|
|
9
|
+
const PRESETS = {
|
|
10
|
+
'fade-in': {
|
|
11
|
+
from: { opacity: 0 },
|
|
12
|
+
to: { opacity: 1 },
|
|
13
|
+
},
|
|
14
|
+
'fade-in-up': {
|
|
15
|
+
from: { opacity: 0, transform: 'translateY(40px)' },
|
|
16
|
+
to: { opacity: 1, transform: 'translateY(0)' },
|
|
17
|
+
},
|
|
18
|
+
'fade-in-down': {
|
|
19
|
+
from: { opacity: 0, transform: 'translateY(-40px)' },
|
|
20
|
+
to: { opacity: 1, transform: 'translateY(0)' },
|
|
21
|
+
},
|
|
22
|
+
'fade-in-left': {
|
|
23
|
+
from: { opacity: 0, transform: 'translateX(-40px)' },
|
|
24
|
+
to: { opacity: 1, transform: 'translateX(0)' },
|
|
25
|
+
},
|
|
26
|
+
'fade-in-right': {
|
|
27
|
+
from: { opacity: 0, transform: 'translateX(40px)' },
|
|
28
|
+
to: { opacity: 1, transform: 'translateX(0)' },
|
|
29
|
+
},
|
|
30
|
+
'zoom-in': {
|
|
31
|
+
from: { opacity: 0, transform: 'scale(0.8)' },
|
|
32
|
+
to: { opacity: 1, transform: 'scale(1)' },
|
|
33
|
+
},
|
|
34
|
+
'zoom-out': {
|
|
35
|
+
from: { opacity: 0, transform: 'scale(1.2)' },
|
|
36
|
+
to: { opacity: 1, transform: 'scale(1)' },
|
|
37
|
+
},
|
|
38
|
+
'flip-x': {
|
|
39
|
+
from: { opacity: 0, transform: 'rotateX(-90deg)' },
|
|
40
|
+
to: { opacity: 1, transform: 'rotateX(0)' },
|
|
41
|
+
},
|
|
42
|
+
'flip-y': {
|
|
43
|
+
from: { opacity: 0, transform: 'rotateY(-90deg)' },
|
|
44
|
+
to: { opacity: 1, transform: 'rotateY(0)' },
|
|
45
|
+
},
|
|
46
|
+
'slide-up': {
|
|
47
|
+
from: { transform: 'translateY(100%)' },
|
|
48
|
+
to: { transform: 'translateY(0)' },
|
|
49
|
+
},
|
|
50
|
+
'slide-down': {
|
|
51
|
+
from: { transform: 'translateY(-100%)' },
|
|
52
|
+
to: { transform: 'translateY(0)' },
|
|
53
|
+
},
|
|
54
|
+
'slide-left': {
|
|
55
|
+
from: { transform: 'translateX(-100%)' },
|
|
56
|
+
to: { transform: 'translateX(0)' },
|
|
57
|
+
},
|
|
58
|
+
'slide-right': {
|
|
59
|
+
from: { transform: 'translateX(100%)' },
|
|
60
|
+
to: { transform: 'translateX(0)' },
|
|
61
|
+
},
|
|
62
|
+
'bounce': {
|
|
63
|
+
from: { opacity: 0, transform: 'translateY(-60px)' },
|
|
64
|
+
to: { opacity: 1, transform: 'translateY(0)' },
|
|
65
|
+
},
|
|
66
|
+
'rotate-in': {
|
|
67
|
+
from: { opacity: 0, transform: 'rotate(-180deg) scale(0.5)' },
|
|
68
|
+
to: { opacity: 1, transform: 'rotate(0deg) scale(1)' },
|
|
69
|
+
},
|
|
70
|
+
'blur-in': {
|
|
71
|
+
from: { opacity: 0, filter: 'blur(12px)' },
|
|
72
|
+
to: { opacity: 1, filter: 'blur(0px)' },
|
|
73
|
+
},
|
|
74
|
+
'skew-in': {
|
|
75
|
+
from: { opacity: 0, transform: 'skewX(20deg) translateX(30px)' },
|
|
76
|
+
to: { opacity: 1, transform: 'skewX(0deg) translateX(0)' },
|
|
77
|
+
},
|
|
78
|
+
'scale-x': {
|
|
79
|
+
from: { transform: 'scaleX(0)' },
|
|
80
|
+
to: { transform: 'scaleX(1)' },
|
|
81
|
+
},
|
|
82
|
+
'scale-y': {
|
|
83
|
+
from: { transform: 'scaleY(0)' },
|
|
84
|
+
to: { transform: 'scaleY(1)' },
|
|
85
|
+
},
|
|
86
|
+
'shimmer': {
|
|
87
|
+
from: { opacity: 0.5, filter: 'brightness(1)' },
|
|
88
|
+
to: { opacity: 1, filter: 'brightness(1.5)' },
|
|
89
|
+
},
|
|
90
|
+
'pulse': {
|
|
91
|
+
from: { transform: 'scale(1)' },
|
|
92
|
+
to: { transform: 'scale(1.05)' },
|
|
93
|
+
},
|
|
94
|
+
'swing': {
|
|
95
|
+
from: { transform: 'rotate(-10deg)' },
|
|
96
|
+
to: { transform: 'rotate(10deg)' },
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
function resolvePreset(animation) {
|
|
100
|
+
var _a;
|
|
101
|
+
if (typeof animation === 'string') {
|
|
102
|
+
return (_a = PRESETS[animation]) !== null && _a !== void 0 ? _a : PRESETS['fade-in-up'];
|
|
103
|
+
}
|
|
104
|
+
if (Array.isArray(animation)) {
|
|
105
|
+
const combined = { from: {}, to: {} };
|
|
106
|
+
animation.forEach(name => {
|
|
107
|
+
const preset = PRESETS[name];
|
|
108
|
+
if (preset) {
|
|
109
|
+
Object.entries(preset.from).forEach(([key, val]) => {
|
|
110
|
+
if (key === 'transform' && combined.from[key]) {
|
|
111
|
+
combined.from[key] = `${combined.from[key]} ${val}`;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
combined.from[key] = val;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
Object.entries(preset.to).forEach(([key, val]) => {
|
|
118
|
+
if (key === 'transform' && combined.to[key]) {
|
|
119
|
+
combined.to[key] = `${combined.to[key]} ${val}`;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
combined.to[key] = val;
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
return combined;
|
|
128
|
+
}
|
|
129
|
+
return animation;
|
|
130
|
+
}
|
|
131
|
+
/** Easing to CSS cubic-bezier mapping */
|
|
132
|
+
const EASING_MAP = {
|
|
133
|
+
linear: 'linear',
|
|
134
|
+
ease: 'ease',
|
|
135
|
+
'ease-in': 'ease-in',
|
|
136
|
+
'ease-out': 'ease-out',
|
|
137
|
+
'ease-in-out': 'ease-in-out',
|
|
138
|
+
spring: 'cubic-bezier(0.34, 1.56, 0.64, 1)',
|
|
139
|
+
};
|
|
140
|
+
function resolveEasing(easing) {
|
|
141
|
+
var _a;
|
|
142
|
+
return (_a = EASING_MAP[easing]) !== null && _a !== void 0 ? _a : easing;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* use-scroll-animate - Core Implementation
|
|
147
|
+
* Uses IntersectionObserver + Web Animations API for zero-dependency,
|
|
148
|
+
* high-performance scroll-triggered animations.
|
|
149
|
+
*/
|
|
150
|
+
const DEFAULT_CONFIG = {
|
|
151
|
+
defaultAnimation: 'fade-in-up',
|
|
152
|
+
defaultDuration: 600,
|
|
153
|
+
defaultDelay: 0,
|
|
154
|
+
defaultEasing: 'ease',
|
|
155
|
+
defaultThreshold: 0.1,
|
|
156
|
+
defaultRootMargin: '0px',
|
|
157
|
+
defaultRepeat: false,
|
|
158
|
+
defaultOnce: true,
|
|
159
|
+
defaultOffset: 0,
|
|
160
|
+
hiddenClass: 'sa-hidden',
|
|
161
|
+
visibleClass: 'sa-visible',
|
|
162
|
+
useClassNames: false,
|
|
163
|
+
disabled: false,
|
|
164
|
+
root: null,
|
|
165
|
+
};
|
|
166
|
+
const DEFAULT_OPTIONS = {
|
|
167
|
+
stagger: 0,
|
|
168
|
+
parallax: {},
|
|
169
|
+
onStart: () => undefined,
|
|
170
|
+
onComplete: () => undefined,
|
|
171
|
+
onEnter: () => undefined,
|
|
172
|
+
onLeave: () => undefined,
|
|
173
|
+
onProgress: () => undefined,
|
|
174
|
+
};
|
|
175
|
+
function parseDataAttributes(el, config) {
|
|
176
|
+
const dataset = el.dataset;
|
|
177
|
+
const opts = {};
|
|
178
|
+
if (dataset.saAnimation) {
|
|
179
|
+
const anim = dataset.saAnimation;
|
|
180
|
+
opts.animation = anim.includes(',')
|
|
181
|
+
? anim.split(',').map(s => s.trim())
|
|
182
|
+
: anim;
|
|
183
|
+
}
|
|
184
|
+
if (dataset.saDuration)
|
|
185
|
+
opts.duration = parseInt(dataset.saDuration, 10);
|
|
186
|
+
if (dataset.saDelay)
|
|
187
|
+
opts.delay = parseInt(dataset.saDelay, 10);
|
|
188
|
+
if (dataset.saEasing)
|
|
189
|
+
opts.easing = dataset.saEasing;
|
|
190
|
+
if (dataset.saThreshold) {
|
|
191
|
+
const t = dataset.saThreshold;
|
|
192
|
+
opts.threshold = t.includes(',') ? t.split(',').map(parseFloat) : parseFloat(t);
|
|
193
|
+
}
|
|
194
|
+
if (dataset.saRootMargin)
|
|
195
|
+
opts.rootMargin = dataset.saRootMargin;
|
|
196
|
+
if (dataset.saRepeat !== undefined)
|
|
197
|
+
opts.repeat = dataset.saRepeat !== 'false';
|
|
198
|
+
if (dataset.saOnce !== undefined)
|
|
199
|
+
opts.once = dataset.saOnce !== 'false';
|
|
200
|
+
if (dataset.saOffset)
|
|
201
|
+
opts.offset = parseInt(dataset.saOffset, 10);
|
|
202
|
+
if (dataset.saStagger)
|
|
203
|
+
opts.stagger = parseInt(dataset.saStagger, 10);
|
|
204
|
+
if (dataset.saParallaxX || dataset.saParallaxY || dataset.saParallaxRotate || dataset.saParallaxScale) {
|
|
205
|
+
opts.parallax = {
|
|
206
|
+
x: dataset.saParallaxX,
|
|
207
|
+
y: dataset.saParallaxY,
|
|
208
|
+
rotate: dataset.saParallaxRotate ? parseFloat(dataset.saParallaxRotate) : undefined,
|
|
209
|
+
scale: dataset.saParallaxScale ? parseFloat(dataset.saParallaxScale) : undefined,
|
|
210
|
+
speed: dataset.saParallaxSpeed ? parseFloat(dataset.saParallaxSpeed) : 1,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
return mergeOptions(opts, config);
|
|
214
|
+
}
|
|
215
|
+
function mergeOptions(opts, config) {
|
|
216
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
|
|
217
|
+
const repeat = (_a = opts.repeat) !== null && _a !== void 0 ? _a : config.defaultRepeat;
|
|
218
|
+
return {
|
|
219
|
+
animation: (_b = opts.animation) !== null && _b !== void 0 ? _b : config.defaultAnimation,
|
|
220
|
+
duration: (_c = opts.duration) !== null && _c !== void 0 ? _c : config.defaultDuration,
|
|
221
|
+
delay: (_d = opts.delay) !== null && _d !== void 0 ? _d : config.defaultDelay,
|
|
222
|
+
easing: (_e = opts.easing) !== null && _e !== void 0 ? _e : config.defaultEasing,
|
|
223
|
+
threshold: (_f = opts.threshold) !== null && _f !== void 0 ? _f : config.defaultThreshold,
|
|
224
|
+
rootMargin: (_g = opts.rootMargin) !== null && _g !== void 0 ? _g : config.defaultRootMargin,
|
|
225
|
+
repeat: repeat,
|
|
226
|
+
once: (_h = opts.once) !== null && _h !== void 0 ? _h : (repeat ? false : config.defaultOnce),
|
|
227
|
+
offset: (_j = opts.offset) !== null && _j !== void 0 ? _j : config.defaultOffset,
|
|
228
|
+
stagger: (_k = opts.stagger) !== null && _k !== void 0 ? _k : DEFAULT_OPTIONS.stagger,
|
|
229
|
+
parallax: (_l = opts.parallax) !== null && _l !== void 0 ? _l : DEFAULT_OPTIONS.parallax,
|
|
230
|
+
onStart: (_m = opts.onStart) !== null && _m !== void 0 ? _m : DEFAULT_OPTIONS.onStart,
|
|
231
|
+
onComplete: (_o = opts.onComplete) !== null && _o !== void 0 ? _o : DEFAULT_OPTIONS.onComplete,
|
|
232
|
+
onEnter: (_p = opts.onEnter) !== null && _p !== void 0 ? _p : DEFAULT_OPTIONS.onEnter,
|
|
233
|
+
onLeave: (_q = opts.onLeave) !== null && _q !== void 0 ? _q : DEFAULT_OPTIONS.onLeave,
|
|
234
|
+
onProgress: (_r = opts.onProgress) !== null && _r !== void 0 ? _r : DEFAULT_OPTIONS.onProgress,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function resolveTargets(target) {
|
|
238
|
+
if (typeof target === 'string') {
|
|
239
|
+
return Array.from(document.querySelectorAll(target));
|
|
240
|
+
}
|
|
241
|
+
if (target instanceof Element) {
|
|
242
|
+
return [target];
|
|
243
|
+
}
|
|
244
|
+
if (target instanceof NodeList) {
|
|
245
|
+
return Array.from(target);
|
|
246
|
+
}
|
|
247
|
+
if (Array.isArray(target)) {
|
|
248
|
+
return target;
|
|
249
|
+
}
|
|
250
|
+
return [];
|
|
251
|
+
}
|
|
252
|
+
function prefersReducedMotion() {
|
|
253
|
+
var _a, _b;
|
|
254
|
+
return (_b = (_a = window.matchMedia) === null || _a === void 0 ? void 0 : _a.call(window, '(prefers-reduced-motion: reduce)').matches) !== null && _b !== void 0 ? _b : false;
|
|
255
|
+
}
|
|
256
|
+
function runAnimation(el, opts, config, staggerIndex = 0) {
|
|
257
|
+
const { animation, duration, delay, easing, stagger, onStart, onComplete } = opts;
|
|
258
|
+
const totalDelay = delay + staggerIndex * stagger;
|
|
259
|
+
const preset = resolvePreset(animation);
|
|
260
|
+
const easingValue = resolveEasing(easing);
|
|
261
|
+
if (config.useClassNames) {
|
|
262
|
+
if (totalDelay > 0) {
|
|
263
|
+
el.style.animationDelay = `${totalDelay}ms`;
|
|
264
|
+
}
|
|
265
|
+
el.classList.remove(config.hiddenClass);
|
|
266
|
+
el.classList.add(config.visibleClass);
|
|
267
|
+
onStart(el);
|
|
268
|
+
setTimeout(() => onComplete(el), duration + totalDelay);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const keyframes = [
|
|
272
|
+
preset.from,
|
|
273
|
+
preset.to,
|
|
274
|
+
];
|
|
275
|
+
const timing = {
|
|
276
|
+
duration,
|
|
277
|
+
delay: totalDelay,
|
|
278
|
+
easing: easingValue,
|
|
279
|
+
fill: 'both',
|
|
280
|
+
};
|
|
281
|
+
// Check if Web Animations API is supported
|
|
282
|
+
if (typeof el.animate === 'function') {
|
|
283
|
+
const anim = el.animate(keyframes, timing);
|
|
284
|
+
onStart(el);
|
|
285
|
+
anim.onfinish = () => onComplete(el);
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
// Fallback for older browsers
|
|
289
|
+
el.style.opacity = '1';
|
|
290
|
+
el.style.transform = 'none';
|
|
291
|
+
onStart(el);
|
|
292
|
+
onComplete(el);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function applyParallax(el, progress, parallax) {
|
|
296
|
+
const { x = 0, y = 0, rotate = 0, scale = 1, speed = 1 } = parallax;
|
|
297
|
+
const p = (progress - 0.5) * 2 * speed;
|
|
298
|
+
let transform = '';
|
|
299
|
+
if (x)
|
|
300
|
+
transform += ` translateX(${typeof x === 'number' ? x * p + 'px' : 'calc(' + x + ' * ' + p + ')'})`;
|
|
301
|
+
if (y)
|
|
302
|
+
transform += ` translateY(${typeof y === 'number' ? y * p + 'px' : 'calc(' + y + ' * ' + p + ')'})`;
|
|
303
|
+
if (rotate)
|
|
304
|
+
transform += ` rotate(${rotate * p}deg)`;
|
|
305
|
+
if (scale !== 1)
|
|
306
|
+
transform += ` scale(${1 + (scale - 1) * p})`;
|
|
307
|
+
el.style.transform = transform;
|
|
308
|
+
}
|
|
309
|
+
function hideElement(el, config) {
|
|
310
|
+
if (config.useClassNames) {
|
|
311
|
+
el.classList.add(config.hiddenClass);
|
|
312
|
+
el.classList.remove(config.visibleClass);
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
el.style.opacity = '0';
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function createScrollAnimate(userConfig = {}) {
|
|
319
|
+
let config = { ...DEFAULT_CONFIG, ...userConfig };
|
|
320
|
+
const registry = new Map();
|
|
321
|
+
function createObserver(opts) {
|
|
322
|
+
// Handle offset by adjusting rootMargin
|
|
323
|
+
const rootMargin = opts.offset !== 0
|
|
324
|
+
? `${opts.rootMargin.split(' ')[0]} 0px -${opts.offset}px 0px`
|
|
325
|
+
: opts.rootMargin;
|
|
326
|
+
return new IntersectionObserver((entries) => {
|
|
327
|
+
entries.forEach((entry) => {
|
|
328
|
+
var _a;
|
|
329
|
+
const record = registry.get(entry.target);
|
|
330
|
+
if (!record)
|
|
331
|
+
return;
|
|
332
|
+
if (entry.isIntersecting) {
|
|
333
|
+
opts.onEnter(entry.target);
|
|
334
|
+
if (!record.animated || opts.repeat) {
|
|
335
|
+
const parent = entry.target.parentElement;
|
|
336
|
+
let staggerIndex = 0;
|
|
337
|
+
if (parent && opts.stagger > 0) {
|
|
338
|
+
// BUG FIX: Use a more robust way to find index among siblings that are also observed
|
|
339
|
+
const siblings = Array.from(parent.children).filter((c) => registry.has(c));
|
|
340
|
+
staggerIndex = siblings.indexOf(entry.target);
|
|
341
|
+
}
|
|
342
|
+
if (!config.disabled && !prefersReducedMotion()) {
|
|
343
|
+
runAnimation(entry.target, opts, config, staggerIndex);
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
entry.target.style.opacity = '1';
|
|
347
|
+
entry.target.style.transform = '';
|
|
348
|
+
}
|
|
349
|
+
record.animated = true;
|
|
350
|
+
// Handle 'once' functionality
|
|
351
|
+
if (opts.once) {
|
|
352
|
+
record.observer.unobserve(entry.target);
|
|
353
|
+
(_a = record.progressObserver) === null || _a === void 0 ? void 0 : _a.unobserve(entry.target);
|
|
354
|
+
// We don't delete from registry yet to allow other potential interactions
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
opts.onLeave(entry.target);
|
|
360
|
+
if (opts.repeat && record.animated) {
|
|
361
|
+
hideElement(entry.target, config);
|
|
362
|
+
record.animated = false;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}, {
|
|
367
|
+
threshold: typeof opts.threshold === 'number' ? opts.threshold : opts.threshold[0],
|
|
368
|
+
rootMargin: rootMargin,
|
|
369
|
+
root: config.root,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
function createProgressObserver(el, opts) {
|
|
373
|
+
const thresholds = [];
|
|
374
|
+
for (let i = 0; i <= 100; i++)
|
|
375
|
+
thresholds.push(i / 100);
|
|
376
|
+
return new IntersectionObserver((entries) => {
|
|
377
|
+
entries.forEach((entry) => {
|
|
378
|
+
const progress = entry.intersectionRatio;
|
|
379
|
+
opts.onProgress(el, progress);
|
|
380
|
+
if (Object.keys(opts.parallax).length > 0) {
|
|
381
|
+
applyParallax(el, progress, opts.parallax);
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
}, {
|
|
385
|
+
threshold: thresholds,
|
|
386
|
+
rootMargin: opts.rootMargin,
|
|
387
|
+
root: config.root,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
function observeElement(el, opts) {
|
|
391
|
+
if (registry.has(el))
|
|
392
|
+
return;
|
|
393
|
+
if (!config.useClassNames) {
|
|
394
|
+
hideElement(el, config);
|
|
395
|
+
}
|
|
396
|
+
const observer = createObserver(opts);
|
|
397
|
+
observer.observe(el);
|
|
398
|
+
let progressObserver;
|
|
399
|
+
if (Object.keys(opts.parallax).length > 0 || opts.onProgress !== DEFAULT_OPTIONS.onProgress) {
|
|
400
|
+
progressObserver = createProgressObserver(el, opts);
|
|
401
|
+
progressObserver.observe(el);
|
|
402
|
+
}
|
|
403
|
+
registry.set(el, {
|
|
404
|
+
element: el,
|
|
405
|
+
options: opts,
|
|
406
|
+
observer,
|
|
407
|
+
animated: false,
|
|
408
|
+
progressObserver
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
const instance = {
|
|
412
|
+
observe(target, options = {}) {
|
|
413
|
+
const elements = resolveTargets(target);
|
|
414
|
+
const opts = mergeOptions(options, config);
|
|
415
|
+
elements.forEach((el) => observeElement(el, opts));
|
|
416
|
+
},
|
|
417
|
+
unobserve(target) {
|
|
418
|
+
const elements = resolveTargets(target);
|
|
419
|
+
elements.forEach((el) => {
|
|
420
|
+
var _a;
|
|
421
|
+
const record = registry.get(el);
|
|
422
|
+
if (record) {
|
|
423
|
+
record.observer.disconnect(); // BUG FIX: Use disconnect for better cleanup
|
|
424
|
+
(_a = record.progressObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
425
|
+
registry.delete(el);
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
},
|
|
429
|
+
init(rootElement = document) {
|
|
430
|
+
const elements = rootElement.querySelectorAll('[data-sa]');
|
|
431
|
+
elements.forEach((el) => {
|
|
432
|
+
const opts = parseDataAttributes(el, config);
|
|
433
|
+
observeElement(el, opts);
|
|
434
|
+
});
|
|
435
|
+
},
|
|
436
|
+
destroy() {
|
|
437
|
+
registry.forEach((record) => {
|
|
438
|
+
var _a;
|
|
439
|
+
record.observer.disconnect();
|
|
440
|
+
(_a = record.progressObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
441
|
+
});
|
|
442
|
+
registry.clear();
|
|
443
|
+
},
|
|
444
|
+
refresh() {
|
|
445
|
+
const entries = Array.from(registry.entries());
|
|
446
|
+
instance.destroy();
|
|
447
|
+
entries.forEach(([el, record]) => {
|
|
448
|
+
observeElement(el, record.options);
|
|
449
|
+
});
|
|
450
|
+
},
|
|
451
|
+
animate(target, options = {}) {
|
|
452
|
+
const elements = resolveTargets(target);
|
|
453
|
+
const opts = mergeOptions(options, config);
|
|
454
|
+
elements.forEach((el) => {
|
|
455
|
+
runAnimation(el, opts, config, 0);
|
|
456
|
+
});
|
|
457
|
+
},
|
|
458
|
+
getObservedElements() {
|
|
459
|
+
return Array.from(registry.values());
|
|
460
|
+
},
|
|
461
|
+
configure(newConfig) {
|
|
462
|
+
config = { ...config, ...newConfig };
|
|
463
|
+
},
|
|
464
|
+
};
|
|
465
|
+
return instance;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* use-scroll-animate - React Integration
|
|
470
|
+
* Provides useScrollAnimate and useScrollRef hooks for React applications.
|
|
471
|
+
*/
|
|
472
|
+
function createReactHooks(React) {
|
|
473
|
+
function useScrollAnimate(options = {}) {
|
|
474
|
+
const ref = React.useRef(null);
|
|
475
|
+
React.useEffect(() => {
|
|
476
|
+
const el = ref.current;
|
|
477
|
+
if (!el)
|
|
478
|
+
return;
|
|
479
|
+
const { animation = 'fade-in-up', duration = 600, delay = 0, easing = 'ease', threshold = 0.1, rootMargin = '0px', repeat = false, parallax = {}, onStart, onComplete, onEnter, onLeave, onProgress, } = options;
|
|
480
|
+
const preset = resolvePreset(animation);
|
|
481
|
+
const easingValue = resolveEasing(easing);
|
|
482
|
+
let animated = false;
|
|
483
|
+
el.style.opacity = '0';
|
|
484
|
+
const observer = new IntersectionObserver((entries) => {
|
|
485
|
+
entries.forEach((entry) => {
|
|
486
|
+
if (entry.isIntersecting) {
|
|
487
|
+
onEnter === null || onEnter === void 0 ? void 0 : onEnter(el);
|
|
488
|
+
if (!animated || repeat) {
|
|
489
|
+
const keyframes = [
|
|
490
|
+
preset.from,
|
|
491
|
+
preset.to,
|
|
492
|
+
];
|
|
493
|
+
const anim = el.animate(keyframes, {
|
|
494
|
+
duration,
|
|
495
|
+
delay,
|
|
496
|
+
easing: easingValue,
|
|
497
|
+
fill: 'both',
|
|
498
|
+
});
|
|
499
|
+
onStart === null || onStart === void 0 ? void 0 : onStart(el);
|
|
500
|
+
anim.onfinish = () => onComplete === null || onComplete === void 0 ? void 0 : onComplete(el);
|
|
501
|
+
animated = true;
|
|
502
|
+
if (!repeat && !Object.keys(parallax).length && !onProgress) {
|
|
503
|
+
observer.unobserve(el);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
onLeave === null || onLeave === void 0 ? void 0 : onLeave(el);
|
|
509
|
+
if (repeat && animated) {
|
|
510
|
+
el.style.opacity = '0';
|
|
511
|
+
animated = false;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
}, { threshold: typeof threshold === 'number' ? threshold : threshold[0], rootMargin });
|
|
516
|
+
let progressObserver = null;
|
|
517
|
+
if (Object.keys(parallax).length > 0 || onProgress) {
|
|
518
|
+
const thresholds = [];
|
|
519
|
+
for (let i = 0; i <= 100; i++)
|
|
520
|
+
thresholds.push(i / 100);
|
|
521
|
+
progressObserver = new IntersectionObserver((entries) => {
|
|
522
|
+
entries.forEach((entry) => {
|
|
523
|
+
const progress = entry.intersectionRatio;
|
|
524
|
+
onProgress === null || onProgress === void 0 ? void 0 : onProgress(el, progress);
|
|
525
|
+
if (Object.keys(parallax).length > 0) {
|
|
526
|
+
const { x = 0, y = 0, rotate = 0, scale = 1, speed = 1 } = parallax;
|
|
527
|
+
const p = (progress - 0.5) * 2 * speed;
|
|
528
|
+
let transform = '';
|
|
529
|
+
if (x)
|
|
530
|
+
transform += ` translateX(${typeof x === 'number' ? x * p + 'px' : 'calc(' + x + ' * ' + p + ')'})`;
|
|
531
|
+
if (y)
|
|
532
|
+
transform += ` translateY(${typeof y === 'number' ? y * p + 'px' : 'calc(' + y + ' * ' + p + ')'})`;
|
|
533
|
+
if (rotate)
|
|
534
|
+
transform += ` rotate(${rotate * p}deg)`;
|
|
535
|
+
if (scale !== 1)
|
|
536
|
+
transform += ` scale(${1 + (scale - 1) * p})`;
|
|
537
|
+
el.style.transform = transform;
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
}, { threshold: thresholds, rootMargin });
|
|
541
|
+
progressObserver.observe(el);
|
|
542
|
+
}
|
|
543
|
+
observer.observe(el);
|
|
544
|
+
return () => {
|
|
545
|
+
observer.disconnect();
|
|
546
|
+
progressObserver === null || progressObserver === void 0 ? void 0 : progressObserver.disconnect();
|
|
547
|
+
};
|
|
548
|
+
}, []);
|
|
549
|
+
return ref;
|
|
550
|
+
}
|
|
551
|
+
function useScrollStagger(options = {}) {
|
|
552
|
+
const ref = React.useRef(null);
|
|
553
|
+
React.useEffect(() => {
|
|
554
|
+
const container = ref.current;
|
|
555
|
+
if (!container)
|
|
556
|
+
return;
|
|
557
|
+
const { animation = 'fade-in-up', duration = 600, delay = 0, easing = 'ease', threshold = 0.1, rootMargin = '0px', stagger = 80, } = options;
|
|
558
|
+
const children = Array.from(container.children);
|
|
559
|
+
const preset = resolvePreset(animation);
|
|
560
|
+
const easingValue = resolveEasing(easing);
|
|
561
|
+
children.forEach((child) => {
|
|
562
|
+
child.style.opacity = '0';
|
|
563
|
+
});
|
|
564
|
+
let animated = false;
|
|
565
|
+
const observer = new IntersectionObserver((entries) => {
|
|
566
|
+
entries.forEach((entry) => {
|
|
567
|
+
if (entry.isIntersecting && !animated) {
|
|
568
|
+
animated = true;
|
|
569
|
+
children.forEach((child, i) => {
|
|
570
|
+
child.animate([preset.from, preset.to], {
|
|
571
|
+
duration,
|
|
572
|
+
delay: delay + i * stagger,
|
|
573
|
+
easing: easingValue,
|
|
574
|
+
fill: 'both',
|
|
575
|
+
});
|
|
576
|
+
});
|
|
577
|
+
observer.unobserve(container);
|
|
578
|
+
}
|
|
579
|
+
});
|
|
580
|
+
}, { threshold: typeof threshold === 'number' ? threshold : threshold[0], rootMargin });
|
|
581
|
+
observer.observe(container);
|
|
582
|
+
return () => observer.disconnect();
|
|
583
|
+
}, []);
|
|
584
|
+
return ref;
|
|
585
|
+
}
|
|
586
|
+
return { useScrollAnimate, useScrollStagger };
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* use-scroll-animate - Vue 3 Integration
|
|
591
|
+
* Provides useScrollAnimate composable for Vue 3 applications.
|
|
592
|
+
*/
|
|
593
|
+
function createVueComposables(Vue) {
|
|
594
|
+
function useScrollAnimate(options = {}) {
|
|
595
|
+
const animateRef = Vue.ref(null);
|
|
596
|
+
let observer = null;
|
|
597
|
+
let progressObserver = null;
|
|
598
|
+
Vue.onMounted(() => {
|
|
599
|
+
const el = animateRef.value;
|
|
600
|
+
if (!el)
|
|
601
|
+
return;
|
|
602
|
+
const { animation = 'fade-in-up', duration = 600, delay = 0, easing = 'ease', threshold = 0.1, rootMargin = '0px', repeat = false, parallax = {}, onStart, onComplete, onEnter, onLeave, onProgress, } = options;
|
|
603
|
+
const preset = resolvePreset(animation);
|
|
604
|
+
const easingValue = resolveEasing(easing);
|
|
605
|
+
let animated = false;
|
|
606
|
+
el.style.opacity = '0';
|
|
607
|
+
observer = new IntersectionObserver((entries) => {
|
|
608
|
+
entries.forEach((entry) => {
|
|
609
|
+
if (entry.isIntersecting) {
|
|
610
|
+
onEnter === null || onEnter === void 0 ? void 0 : onEnter(el);
|
|
611
|
+
if (!animated || repeat) {
|
|
612
|
+
const anim = el.animate([preset.from, preset.to], {
|
|
613
|
+
duration,
|
|
614
|
+
delay,
|
|
615
|
+
easing: easingValue,
|
|
616
|
+
fill: 'both',
|
|
617
|
+
});
|
|
618
|
+
onStart === null || onStart === void 0 ? void 0 : onStart(el);
|
|
619
|
+
anim.onfinish = () => onComplete === null || onComplete === void 0 ? void 0 : onComplete(el);
|
|
620
|
+
animated = true;
|
|
621
|
+
if (!repeat && !Object.keys(parallax).length && !onProgress) {
|
|
622
|
+
observer === null || observer === void 0 ? void 0 : observer.unobserve(el);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
else {
|
|
627
|
+
onLeave === null || onLeave === void 0 ? void 0 : onLeave(el);
|
|
628
|
+
if (repeat && animated) {
|
|
629
|
+
el.style.opacity = '0';
|
|
630
|
+
animated = false;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
}, { threshold: typeof threshold === 'number' ? threshold : threshold[0], rootMargin });
|
|
635
|
+
if (Object.keys(parallax).length > 0 || onProgress) {
|
|
636
|
+
const thresholds = [];
|
|
637
|
+
for (let i = 0; i <= 100; i++)
|
|
638
|
+
thresholds.push(i / 100);
|
|
639
|
+
progressObserver = new IntersectionObserver((entries) => {
|
|
640
|
+
entries.forEach((entry) => {
|
|
641
|
+
const progress = entry.intersectionRatio;
|
|
642
|
+
onProgress === null || onProgress === void 0 ? void 0 : onProgress(el, progress);
|
|
643
|
+
if (Object.keys(parallax).length > 0) {
|
|
644
|
+
const { x = 0, y = 0, rotate = 0, scale = 1, speed = 1 } = parallax;
|
|
645
|
+
const p = (progress - 0.5) * 2 * speed;
|
|
646
|
+
let transform = '';
|
|
647
|
+
if (x)
|
|
648
|
+
transform += ` translateX(${typeof x === 'number' ? x * p + 'px' : 'calc(' + x + ' * ' + p + ')'})`;
|
|
649
|
+
if (y)
|
|
650
|
+
transform += ` translateY(${typeof y === 'number' ? y * p + 'px' : 'calc(' + y + ' * ' + p + ')'})`;
|
|
651
|
+
if (rotate)
|
|
652
|
+
transform += ` rotate(${rotate * p}deg)`;
|
|
653
|
+
if (scale !== 1)
|
|
654
|
+
transform += ` scale(${1 + (scale - 1) * p})`;
|
|
655
|
+
el.style.transform = transform;
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
}, { threshold: thresholds, rootMargin });
|
|
659
|
+
progressObserver.observe(el);
|
|
660
|
+
}
|
|
661
|
+
observer.observe(el);
|
|
662
|
+
});
|
|
663
|
+
Vue.onUnmounted(() => {
|
|
664
|
+
observer === null || observer === void 0 ? void 0 : observer.disconnect();
|
|
665
|
+
progressObserver === null || progressObserver === void 0 ? void 0 : progressObserver.disconnect();
|
|
666
|
+
});
|
|
667
|
+
return { animateRef };
|
|
668
|
+
}
|
|
669
|
+
return { useScrollAnimate };
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* use-scroll-animate
|
|
674
|
+
*
|
|
675
|
+
* A lightweight (~4KB gzipped), dependency-free scroll animation library
|
|
676
|
+
* for modern web applications. Built with TypeScript, powered by
|
|
677
|
+
* IntersectionObserver and the Web Animations API.
|
|
678
|
+
*
|
|
679
|
+
* @version 1.0.0
|
|
680
|
+
* @license MIT
|
|
681
|
+
* @see https://github.com/use-scroll-animate/use-scroll-animate
|
|
682
|
+
*/
|
|
683
|
+
/**
|
|
684
|
+
* Default singleton instance of ScrollAnimate.
|
|
685
|
+
* Ready to use out of the box with sensible defaults.
|
|
686
|
+
*
|
|
687
|
+
* @example
|
|
688
|
+
* ```js
|
|
689
|
+
* import ScrollAnimate from 'use-scroll-animate';
|
|
690
|
+
*
|
|
691
|
+
* // Auto-initialize all elements with data-sa attribute
|
|
692
|
+
* ScrollAnimate.init();
|
|
693
|
+
*
|
|
694
|
+
* // Or manually observe elements
|
|
695
|
+
* ScrollAnimate.observe('.my-element', { animation: 'fade-in-up' });
|
|
696
|
+
* ```
|
|
697
|
+
*/
|
|
698
|
+
const ScrollAnimate = createScrollAnimate();
|
|
699
|
+
|
|
700
|
+
exports.EASING_MAP = EASING_MAP;
|
|
701
|
+
exports.PRESETS = PRESETS;
|
|
702
|
+
exports.createReactHooks = createReactHooks;
|
|
703
|
+
exports.createScrollAnimate = createScrollAnimate;
|
|
704
|
+
exports.createVueComposables = createVueComposables;
|
|
705
|
+
exports.default = ScrollAnimate;
|
|
706
|
+
exports.resolveEasing = resolveEasing;
|
|
707
|
+
exports.resolvePreset = resolvePreset;
|
|
708
|
+
//# sourceMappingURL=index.js.map
|