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
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>use-scroll-animate — Vanilla JS Demo</title>
|
|
7
|
-
<style>
|
|
8
|
-
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
9
|
-
body { font-family: system-ui, sans-serif; background: #0a0a0a; color: #fff; }
|
|
10
|
-
.hero {
|
|
11
|
-
height: 100vh;
|
|
12
|
-
display: flex;
|
|
13
|
-
flex-direction: column;
|
|
14
|
-
align-items: center;
|
|
15
|
-
justify-content: center;
|
|
16
|
-
text-align: center;
|
|
17
|
-
padding: 2rem;
|
|
18
|
-
overflow: hidden; /* For parallax */
|
|
19
|
-
}
|
|
20
|
-
.hero h1 { font-size: clamp(2rem, 6vw, 5rem); font-weight: 800; }
|
|
21
|
-
.hero p { font-size: 1.2rem; color: #aaa; margin-top: 1rem; }
|
|
22
|
-
.section {
|
|
23
|
-
max-width: 900px;
|
|
24
|
-
margin: 0 auto;
|
|
25
|
-
padding: 6rem 2rem;
|
|
26
|
-
}
|
|
27
|
-
.card {
|
|
28
|
-
background: #1a1a1a;
|
|
29
|
-
border: 1px solid #333;
|
|
30
|
-
border-radius: 12px;
|
|
31
|
-
padding: 2rem;
|
|
32
|
-
margin-bottom: 2rem;
|
|
33
|
-
}
|
|
34
|
-
.card h2 { font-size: 1.5rem; margin-bottom: 0.5rem; }
|
|
35
|
-
.card p { color: #aaa; line-height: 1.6; }
|
|
36
|
-
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1.5rem; }
|
|
37
|
-
.badge {
|
|
38
|
-
background: linear-gradient(135deg, #6366f1, #8b5cf6);
|
|
39
|
-
border-radius: 8px;
|
|
40
|
-
padding: 1.5rem;
|
|
41
|
-
text-align: center;
|
|
42
|
-
}
|
|
43
|
-
.badge h3 { font-size: 1.1rem; margin-bottom: 0.3rem; }
|
|
44
|
-
.badge span { font-size: 0.85rem; opacity: 0.8; }
|
|
45
|
-
.scroll-hint { position: fixed; bottom: 2rem; left: 50%; transform: translateX(-50%); color: #666; font-size: 0.85rem; }
|
|
46
|
-
|
|
47
|
-
/* Parallax specific styles */
|
|
48
|
-
.parallax-bg {
|
|
49
|
-
position: absolute;
|
|
50
|
-
top: 0;
|
|
51
|
-
left: 0;
|
|
52
|
-
width: 100%;
|
|
53
|
-
height: 100%;
|
|
54
|
-
background: url("https://picsum.photos/1920/1080?random=1") no-repeat center center/cover;
|
|
55
|
-
z-index: -1;
|
|
56
|
-
}
|
|
57
|
-
.parallax-text {
|
|
58
|
-
font-size: 3rem;
|
|
59
|
-
font-weight: bold;
|
|
60
|
-
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
|
|
61
|
-
margin-top: 50vh;
|
|
62
|
-
text-align: center;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/* Progress bar styles */
|
|
66
|
-
.progress-container {
|
|
67
|
-
width: 80%;
|
|
68
|
-
height: 20px;
|
|
69
|
-
background-color: #333;
|
|
70
|
-
border-radius: 10px;
|
|
71
|
-
margin: 2rem auto;
|
|
72
|
-
overflow: hidden;
|
|
73
|
-
}
|
|
74
|
-
.progress-bar {
|
|
75
|
-
height: 100%;
|
|
76
|
-
width: 0%;
|
|
77
|
-
background-color: #4CAF50;
|
|
78
|
-
border-radius: 10px;
|
|
79
|
-
transition: width 0.1s linear; /* Smooth transition for progress */
|
|
80
|
-
}
|
|
81
|
-
</style>
|
|
82
|
-
</head>
|
|
83
|
-
<body>
|
|
84
|
-
|
|
85
|
-
<div class="hero">
|
|
86
|
-
<div class="parallax-bg" data-sa data-sa-parallax-y="-200px" data-sa-parallax-speed="0.3"></div>
|
|
87
|
-
<h1 data-sa data-sa-animation="fade-in-down, zoom-in" data-sa-duration="1000">
|
|
88
|
-
use-scroll-animate
|
|
89
|
-
</h1>
|
|
90
|
-
<p data-sa data-sa-animation="fade-in-up" data-sa-delay="300">
|
|
91
|
-
Lightweight. Dependency-free. Blazing fast.
|
|
92
|
-
</p>
|
|
93
|
-
</div>
|
|
94
|
-
|
|
95
|
-
<div class="section">
|
|
96
|
-
<div class="card" data-sa data-sa-animation="fade-in-left">
|
|
97
|
-
<h2>Zero Dependencies</h2>
|
|
98
|
-
<p>Built entirely on native browser APIs — IntersectionObserver and the Web Animations API. No lodash, no jQuery, no bloat.</p>
|
|
99
|
-
</div>
|
|
100
|
-
|
|
101
|
-
<div class="card" data-sa data-sa-animation="fade-in-right" data-sa-delay="100">
|
|
102
|
-
<h2>TypeScript First</h2>
|
|
103
|
-
<p>Written in TypeScript with comprehensive type definitions. Enjoy full IntelliSense and type safety in your IDE.</p>
|
|
104
|
-
</div>
|
|
105
|
-
|
|
106
|
-
<div class="card" data-sa data-sa-animation="zoom-in" data-sa-delay="200">
|
|
107
|
-
<h2>Framework Agnostic</h2>
|
|
108
|
-
<p>Works with Vanilla JS, React, Vue, Svelte, and any other framework. First-class hooks and composables included.</p>
|
|
109
|
-
</div>
|
|
110
|
-
</div>
|
|
111
|
-
|
|
112
|
-
<div class="section">
|
|
113
|
-
<h2 style="text-align: center; margin-bottom: 2rem;" data-sa data-sa-animation="fade-in">New in v1.1.0</h2>
|
|
114
|
-
|
|
115
|
-
<div class="card" data-sa data-sa-animation="slide-left" data-sa-delay="0">
|
|
116
|
-
<h2>Multiple Animations</h2>
|
|
117
|
-
<p>Combine presets like `fade-in-up, zoom-in` for richer effects.</p>
|
|
118
|
-
</div>
|
|
119
|
-
|
|
120
|
-
<div class="card" data-sa data-sa-animation="slide-right" data-sa-delay="100">
|
|
121
|
-
<h2>Parallax Effect</h2>
|
|
122
|
-
<p>Elements can move, rotate, or scale based on scroll position. Try scrolling!</p>
|
|
123
|
-
<div style="height: 100px; background: #222; margin-top: 1rem; display: flex; align-items: center; justify-content: center;" data-sa data-sa-parallax-y="-50px" data-sa-parallax-rotate="15" data-sa-parallax-speed="0.8">
|
|
124
|
-
<span style="font-size: 1.5rem;">Parallax Element</span>
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
|
|
128
|
-
<div class="card" data-sa data-sa-animation="fade-in-up" data-sa-delay="200">
|
|
129
|
-
<h2>Scroll Progress Listener</h2>
|
|
130
|
-
<p>Get real-time scroll progress (0 to 1) for any element.</p>
|
|
131
|
-
<div class="progress-container">
|
|
132
|
-
<div class="progress-bar" id="myProgressBar"></div>
|
|
133
|
-
</div>
|
|
134
|
-
<p id="progressText" style="text-align: center;">0%</p>
|
|
135
|
-
</div>
|
|
136
|
-
</div>
|
|
137
|
-
|
|
138
|
-
<div class="section">
|
|
139
|
-
<div class="grid">
|
|
140
|
-
<div class="badge" data-sa data-sa-animation="fade-in-up" data-sa-stagger="80">
|
|
141
|
-
<h3>fade-in-up</h3>
|
|
142
|
-
<span>Classic entrance</span>
|
|
143
|
-
</div>
|
|
144
|
-
<div class="badge" data-sa data-sa-animation="zoom-in" data-sa-stagger="80">
|
|
145
|
-
<h3>zoom-in</h3>
|
|
146
|
-
<span>Scale reveal</span>
|
|
147
|
-
</div>
|
|
148
|
-
<div class="badge" data-sa data-sa-animation="flip-x" data-sa-stagger="80">
|
|
149
|
-
<h3>flip-x</h3>
|
|
150
|
-
<span>3D flip</span>
|
|
151
|
-
</div>
|
|
152
|
-
<div class="badge" data-sa data-sa-animation="blur-in" data-sa-stagger="80">
|
|
153
|
-
<h3>blur-in</h3>
|
|
154
|
-
<span>Focus reveal</span>
|
|
155
|
-
</div>
|
|
156
|
-
<div class="badge" data-sa data-sa-animation="bounce" data-sa-stagger="80">
|
|
157
|
-
<h3>bounce</h3>
|
|
158
|
-
<span>Spring physics</span>
|
|
159
|
-
</div>
|
|
160
|
-
<div class="badge" data-sa data-sa-animation="rotate-in" data-sa-stagger="80">
|
|
161
|
-
<h3>rotate-in</h3>
|
|
162
|
-
<span>Spin entrance</span>
|
|
163
|
-
</div>
|
|
164
|
-
<div class="badge" data-sa data-sa-animation="skew-in" data-sa-stagger="80">
|
|
165
|
-
<h3>skew-in</h3>
|
|
166
|
-
<span>Slanted entrance</span>
|
|
167
|
-
</div>
|
|
168
|
-
<div class="badge" data-sa data-sa-animation="scale-x" data-sa-stagger="80">
|
|
169
|
-
<h3>scale-x</h3>
|
|
170
|
-
<span>Horizontal scale</span>
|
|
171
|
-
</div>
|
|
172
|
-
<div class="badge" data-sa data-sa-animation="scale-y" data-sa-stagger="80">
|
|
173
|
-
<h3>scale-y</h3>
|
|
174
|
-
<span>Vertical scale</span>
|
|
175
|
-
</div>
|
|
176
|
-
</div>
|
|
177
|
-
</div>
|
|
178
|
-
|
|
179
|
-
<p class="scroll-hint">↓ Scroll to see animations</p>
|
|
180
|
-
|
|
181
|
-
<script type="module">
|
|
182
|
-
import ScrollAnimate from '../../dist/index.esm.js';
|
|
183
|
-
|
|
184
|
-
const sa = ScrollAnimate.createScrollAnimate(); // Create a new instance to use onProgress
|
|
185
|
-
|
|
186
|
-
sa.init();
|
|
187
|
-
|
|
188
|
-
// Observe the progress bar for scroll progress
|
|
189
|
-
sa.observe(
|
|
190
|
-
document.getElementById("myProgressBar"),
|
|
191
|
-
{
|
|
192
|
-
onProgress: (el, progress) => {
|
|
193
|
-
(el as HTMLElement).style.width = `${progress * 100}%`;
|
|
194
|
-
document.getElementById("progressText").innerText = `${Math.round(progress * 100)}%`;
|
|
195
|
-
},
|
|
196
|
-
threshold: Array.from({ length: 101 }, (_, i) => i / 100), // Full range for progress
|
|
197
|
-
}
|
|
198
|
-
);
|
|
199
|
-
</script>
|
|
200
|
-
</body>
|
|
201
|
-
</html>
|
package/rollup.config.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
const typescript = require('@rollup/plugin-typescript');
|
|
2
|
-
const resolve = require('@rollup/plugin-node-resolve');
|
|
3
|
-
const commonjs = require('@rollup/plugin-commonjs');
|
|
4
|
-
const terser = require('@rollup/plugin-terser');
|
|
5
|
-
|
|
6
|
-
module.exports = [
|
|
7
|
-
// Main bundle
|
|
8
|
-
{
|
|
9
|
-
input: 'src/index.ts',
|
|
10
|
-
output: [
|
|
11
|
-
{
|
|
12
|
-
file: 'dist/index.js',
|
|
13
|
-
format: 'cjs',
|
|
14
|
-
sourcemap: true,
|
|
15
|
-
exports: 'named',
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
file: 'dist/index.esm.js',
|
|
19
|
-
format: 'es',
|
|
20
|
-
sourcemap: true,
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
file: 'dist/index.umd.js',
|
|
24
|
-
format: 'umd',
|
|
25
|
-
name: 'ScrollAnimate',
|
|
26
|
-
sourcemap: true,
|
|
27
|
-
plugins: [terser()],
|
|
28
|
-
},
|
|
29
|
-
],
|
|
30
|
-
plugins: [
|
|
31
|
-
resolve(),
|
|
32
|
-
commonjs(),
|
|
33
|
-
typescript({
|
|
34
|
-
tsconfig: './tsconfig.json',
|
|
35
|
-
declaration: true,
|
|
36
|
-
declarationDir: './dist/types',
|
|
37
|
-
rootDir: './src',
|
|
38
|
-
}),
|
|
39
|
-
],
|
|
40
|
-
},
|
|
41
|
-
];
|
package/src/core.ts
DELETED
|
@@ -1,373 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* use-scroll-animate - Core Implementation
|
|
3
|
-
* Uses IntersectionObserver + Web Animations API for zero-dependency,
|
|
4
|
-
* high-performance scroll-triggered animations.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type {
|
|
8
|
-
AnimateOptions,
|
|
9
|
-
AnimatedElement,
|
|
10
|
-
ScrollAnimateConfig,
|
|
11
|
-
ScrollAnimateInstance,
|
|
12
|
-
ParallaxOptions,
|
|
13
|
-
} from './types';
|
|
14
|
-
import { resolvePreset, resolveEasing } from './presets';
|
|
15
|
-
|
|
16
|
-
const DEFAULT_CONFIG: Required<ScrollAnimateConfig> = {
|
|
17
|
-
defaultAnimation: 'fade-in-up',
|
|
18
|
-
defaultDuration: 600,
|
|
19
|
-
defaultDelay: 0,
|
|
20
|
-
defaultEasing: 'ease',
|
|
21
|
-
defaultThreshold: 0.1,
|
|
22
|
-
defaultRootMargin: '0px',
|
|
23
|
-
defaultRepeat: false,
|
|
24
|
-
defaultOnce: true,
|
|
25
|
-
defaultOffset: 0,
|
|
26
|
-
hiddenClass: 'sa-hidden',
|
|
27
|
-
visibleClass: 'sa-visible',
|
|
28
|
-
useClassNames: false,
|
|
29
|
-
disabled: false,
|
|
30
|
-
root: null,
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const DEFAULT_OPTIONS: Required<AnimateOptions> = {
|
|
34
|
-
animation: 'fade-in-up',
|
|
35
|
-
duration: 600,
|
|
36
|
-
delay: 0,
|
|
37
|
-
easing: 'ease',
|
|
38
|
-
threshold: 0.1,
|
|
39
|
-
rootMargin: '0px',
|
|
40
|
-
repeat: false,
|
|
41
|
-
once: true,
|
|
42
|
-
offset: 0,
|
|
43
|
-
stagger: 0,
|
|
44
|
-
parallax: {},
|
|
45
|
-
onStart: () => undefined,
|
|
46
|
-
onComplete: () => undefined,
|
|
47
|
-
onEnter: () => undefined,
|
|
48
|
-
onLeave: () => undefined,
|
|
49
|
-
onProgress: () => undefined,
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
function parseDataAttributes(el: Element, config: Required<ScrollAnimateConfig>): Required<AnimateOptions> {
|
|
53
|
-
const dataset = (el as HTMLElement).dataset;
|
|
54
|
-
const opts: AnimateOptions = {};
|
|
55
|
-
|
|
56
|
-
if (dataset.saAnimation) {
|
|
57
|
-
const anim = dataset.saAnimation;
|
|
58
|
-
opts.animation = anim.includes(',')
|
|
59
|
-
? anim.split(',').map(s => s.trim()) as any
|
|
60
|
-
: anim as any;
|
|
61
|
-
}
|
|
62
|
-
if (dataset.saDuration) opts.duration = parseInt(dataset.saDuration, 10);
|
|
63
|
-
if (dataset.saDelay) opts.delay = parseInt(dataset.saDelay, 10);
|
|
64
|
-
if (dataset.saEasing) opts.easing = dataset.saEasing;
|
|
65
|
-
if (dataset.saThreshold) {
|
|
66
|
-
const t = dataset.saThreshold;
|
|
67
|
-
opts.threshold = t.includes(',') ? t.split(',').map(parseFloat) : parseFloat(t);
|
|
68
|
-
}
|
|
69
|
-
if (dataset.saRootMargin) opts.rootMargin = dataset.saRootMargin;
|
|
70
|
-
if (dataset.saRepeat !== undefined) opts.repeat = dataset.saRepeat !== 'false';
|
|
71
|
-
if (dataset.saOnce !== undefined) opts.once = dataset.saOnce !== 'false';
|
|
72
|
-
if (dataset.saOffset) opts.offset = parseInt(dataset.saOffset, 10);
|
|
73
|
-
if (dataset.saStagger) opts.stagger = parseInt(dataset.saStagger, 10);
|
|
74
|
-
|
|
75
|
-
if (dataset.saParallaxX || dataset.saParallaxY || dataset.saParallaxRotate || dataset.saParallaxScale) {
|
|
76
|
-
opts.parallax = {
|
|
77
|
-
x: dataset.saParallaxX,
|
|
78
|
-
y: dataset.saParallaxY,
|
|
79
|
-
rotate: dataset.saParallaxRotate ? parseFloat(dataset.saParallaxRotate) : undefined,
|
|
80
|
-
scale: dataset.saParallaxScale ? parseFloat(dataset.saParallaxScale) : undefined,
|
|
81
|
-
speed: dataset.saParallaxSpeed ? parseFloat(dataset.saParallaxSpeed) : 1,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return mergeOptions(opts, config);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function mergeOptions(
|
|
89
|
-
opts: AnimateOptions,
|
|
90
|
-
config: Required<ScrollAnimateConfig>
|
|
91
|
-
): Required<AnimateOptions> {
|
|
92
|
-
const repeat = opts.repeat ?? config.defaultRepeat;
|
|
93
|
-
return {
|
|
94
|
-
animation: opts.animation ?? config.defaultAnimation,
|
|
95
|
-
duration: opts.duration ?? config.defaultDuration,
|
|
96
|
-
delay: opts.delay ?? config.defaultDelay,
|
|
97
|
-
easing: opts.easing ?? config.defaultEasing,
|
|
98
|
-
threshold: opts.threshold ?? config.defaultThreshold,
|
|
99
|
-
rootMargin: opts.rootMargin ?? config.defaultRootMargin,
|
|
100
|
-
repeat: repeat,
|
|
101
|
-
once: opts.once ?? (repeat ? false : config.defaultOnce),
|
|
102
|
-
offset: opts.offset ?? config.defaultOffset,
|
|
103
|
-
stagger: opts.stagger ?? DEFAULT_OPTIONS.stagger,
|
|
104
|
-
parallax: opts.parallax ?? DEFAULT_OPTIONS.parallax,
|
|
105
|
-
onStart: opts.onStart ?? DEFAULT_OPTIONS.onStart,
|
|
106
|
-
onComplete: opts.onComplete ?? DEFAULT_OPTIONS.onComplete,
|
|
107
|
-
onEnter: opts.onEnter ?? DEFAULT_OPTIONS.onEnter,
|
|
108
|
-
onLeave: opts.onLeave ?? DEFAULT_OPTIONS.onLeave,
|
|
109
|
-
onProgress: opts.onProgress ?? DEFAULT_OPTIONS.onProgress,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function resolveTargets(target: string | Element | NodeList | Element[]): Element[] {
|
|
114
|
-
if (typeof target === 'string') {
|
|
115
|
-
return Array.from(document.querySelectorAll(target));
|
|
116
|
-
}
|
|
117
|
-
if (target instanceof Element) {
|
|
118
|
-
return [target];
|
|
119
|
-
}
|
|
120
|
-
if (target instanceof NodeList) {
|
|
121
|
-
return Array.from(target) as Element[];
|
|
122
|
-
}
|
|
123
|
-
if (Array.isArray(target)) {
|
|
124
|
-
return target;
|
|
125
|
-
}
|
|
126
|
-
return [];
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function prefersReducedMotion(): boolean {
|
|
130
|
-
return window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function runAnimation(
|
|
134
|
-
el: Element,
|
|
135
|
-
opts: Required<AnimateOptions>,
|
|
136
|
-
config: Required<ScrollAnimateConfig>,
|
|
137
|
-
staggerIndex = 0
|
|
138
|
-
): void {
|
|
139
|
-
const { animation, duration, delay, easing, stagger, onStart, onComplete } = opts;
|
|
140
|
-
const totalDelay = delay + staggerIndex * stagger;
|
|
141
|
-
const preset = resolvePreset(animation);
|
|
142
|
-
const easingValue = resolveEasing(easing);
|
|
143
|
-
|
|
144
|
-
if (config.useClassNames) {
|
|
145
|
-
if (totalDelay > 0) {
|
|
146
|
-
(el as HTMLElement).style.animationDelay = `${totalDelay}ms`;
|
|
147
|
-
}
|
|
148
|
-
el.classList.remove(config.hiddenClass);
|
|
149
|
-
el.classList.add(config.visibleClass);
|
|
150
|
-
onStart(el);
|
|
151
|
-
setTimeout(() => onComplete(el), duration + totalDelay);
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const keyframes: Keyframe[] = [
|
|
156
|
-
preset.from as Keyframe,
|
|
157
|
-
preset.to as Keyframe,
|
|
158
|
-
];
|
|
159
|
-
|
|
160
|
-
const timing: KeyframeAnimationOptions = {
|
|
161
|
-
duration,
|
|
162
|
-
delay: totalDelay,
|
|
163
|
-
easing: easingValue,
|
|
164
|
-
fill: 'both',
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
// Check if Web Animations API is supported
|
|
168
|
-
if (typeof el.animate === 'function') {
|
|
169
|
-
const anim = el.animate(keyframes, timing);
|
|
170
|
-
onStart(el);
|
|
171
|
-
anim.onfinish = () => onComplete(el);
|
|
172
|
-
} else {
|
|
173
|
-
// Fallback for older browsers
|
|
174
|
-
(el as HTMLElement).style.opacity = '1';
|
|
175
|
-
(el as HTMLElement).style.transform = 'none';
|
|
176
|
-
onStart(el);
|
|
177
|
-
onComplete(el);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function applyParallax(el: Element, progress: number, parallax: ParallaxOptions): void {
|
|
182
|
-
const { x = 0, y = 0, rotate = 0, scale = 1, speed = 1 } = parallax;
|
|
183
|
-
const p = (progress - 0.5) * 2 * speed;
|
|
184
|
-
|
|
185
|
-
let transform = '';
|
|
186
|
-
if (x) transform += ` translateX(${typeof x === 'number' ? x * p + 'px' : 'calc(' + x + ' * ' + p + ')'})`;
|
|
187
|
-
if (y) transform += ` translateY(${typeof y === 'number' ? y * p + 'px' : 'calc(' + y + ' * ' + p + ')'})`;
|
|
188
|
-
if (rotate) transform += ` rotate(${rotate * p}deg)`;
|
|
189
|
-
if (scale !== 1) transform += ` scale(${1 + (scale - 1) * p})`;
|
|
190
|
-
|
|
191
|
-
(el as HTMLElement).style.transform = transform;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function hideElement(el: Element, config: Required<ScrollAnimateConfig>): void {
|
|
195
|
-
if (config.useClassNames) {
|
|
196
|
-
el.classList.add(config.hiddenClass);
|
|
197
|
-
el.classList.remove(config.visibleClass);
|
|
198
|
-
} else {
|
|
199
|
-
(el as HTMLElement).style.opacity = '0';
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
export function createScrollAnimate(userConfig: ScrollAnimateConfig = {}): ScrollAnimateInstance {
|
|
204
|
-
let config: Required<ScrollAnimateConfig> = { ...DEFAULT_CONFIG, ...userConfig };
|
|
205
|
-
const registry = new Map<Element, AnimatedElement>();
|
|
206
|
-
|
|
207
|
-
function createObserver(opts: Required<AnimateOptions>): IntersectionObserver {
|
|
208
|
-
// Handle offset by adjusting rootMargin
|
|
209
|
-
const rootMargin = opts.offset !== 0
|
|
210
|
-
? `${opts.rootMargin.split(' ')[0]} 0px -${opts.offset}px 0px`
|
|
211
|
-
: opts.rootMargin;
|
|
212
|
-
|
|
213
|
-
return new IntersectionObserver(
|
|
214
|
-
(entries) => {
|
|
215
|
-
entries.forEach((entry) => {
|
|
216
|
-
const record = registry.get(entry.target);
|
|
217
|
-
if (!record) return;
|
|
218
|
-
|
|
219
|
-
if (entry.isIntersecting) {
|
|
220
|
-
opts.onEnter(entry.target);
|
|
221
|
-
|
|
222
|
-
if (!record.animated || opts.repeat) {
|
|
223
|
-
const parent = entry.target.parentElement;
|
|
224
|
-
let staggerIndex = 0;
|
|
225
|
-
if (parent && opts.stagger > 0) {
|
|
226
|
-
// BUG FIX: Use a more robust way to find index among siblings that are also observed
|
|
227
|
-
const siblings = Array.from(parent.children).filter((c) => registry.has(c));
|
|
228
|
-
staggerIndex = siblings.indexOf(entry.target);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (!config.disabled && !prefersReducedMotion()) {
|
|
232
|
-
runAnimation(entry.target, opts, config, staggerIndex);
|
|
233
|
-
} else {
|
|
234
|
-
(entry.target as HTMLElement).style.opacity = '1';
|
|
235
|
-
(entry.target as HTMLElement).style.transform = '';
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
record.animated = true;
|
|
239
|
-
|
|
240
|
-
// Handle 'once' functionality
|
|
241
|
-
if (opts.once) {
|
|
242
|
-
record.observer.unobserve(entry.target);
|
|
243
|
-
record.progressObserver?.unobserve(entry.target);
|
|
244
|
-
// We don't delete from registry yet to allow other potential interactions
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
} else {
|
|
248
|
-
opts.onLeave(entry.target);
|
|
249
|
-
if (opts.repeat && record.animated) {
|
|
250
|
-
hideElement(entry.target, config);
|
|
251
|
-
record.animated = false;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
},
|
|
256
|
-
{
|
|
257
|
-
threshold: typeof opts.threshold === 'number' ? opts.threshold : opts.threshold[0],
|
|
258
|
-
rootMargin: rootMargin,
|
|
259
|
-
root: config.root,
|
|
260
|
-
}
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function createProgressObserver(el: Element, opts: Required<AnimateOptions>): IntersectionObserver {
|
|
265
|
-
const thresholds = [];
|
|
266
|
-
for (let i = 0; i <= 100; i++) thresholds.push(i / 100);
|
|
267
|
-
|
|
268
|
-
return new IntersectionObserver(
|
|
269
|
-
(entries) => {
|
|
270
|
-
entries.forEach((entry) => {
|
|
271
|
-
const progress = entry.intersectionRatio;
|
|
272
|
-
opts.onProgress(el, progress);
|
|
273
|
-
|
|
274
|
-
if (Object.keys(opts.parallax).length > 0) {
|
|
275
|
-
applyParallax(el, progress, opts.parallax);
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
},
|
|
279
|
-
{
|
|
280
|
-
threshold: thresholds,
|
|
281
|
-
rootMargin: opts.rootMargin,
|
|
282
|
-
root: config.root,
|
|
283
|
-
}
|
|
284
|
-
);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function observeElement(el: Element, opts: Required<AnimateOptions>): void {
|
|
288
|
-
if (registry.has(el)) return;
|
|
289
|
-
|
|
290
|
-
if (!config.useClassNames) {
|
|
291
|
-
hideElement(el, config);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
const observer = createObserver(opts);
|
|
295
|
-
observer.observe(el);
|
|
296
|
-
|
|
297
|
-
let progressObserver;
|
|
298
|
-
if (Object.keys(opts.parallax).length > 0 || opts.onProgress !== DEFAULT_OPTIONS.onProgress) {
|
|
299
|
-
progressObserver = createProgressObserver(el, opts);
|
|
300
|
-
progressObserver.observe(el);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
registry.set(el, {
|
|
304
|
-
element: el,
|
|
305
|
-
options: opts,
|
|
306
|
-
observer,
|
|
307
|
-
animated: false,
|
|
308
|
-
progressObserver
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
const instance: ScrollAnimateInstance = {
|
|
313
|
-
observe(target, options = {}) {
|
|
314
|
-
const elements = resolveTargets(target);
|
|
315
|
-
const opts = mergeOptions(options, config);
|
|
316
|
-
elements.forEach((el) => observeElement(el, opts));
|
|
317
|
-
},
|
|
318
|
-
|
|
319
|
-
unobserve(target) {
|
|
320
|
-
const elements = resolveTargets(target);
|
|
321
|
-
elements.forEach((el) => {
|
|
322
|
-
const record = registry.get(el);
|
|
323
|
-
if (record) {
|
|
324
|
-
record.observer.disconnect(); // BUG FIX: Use disconnect for better cleanup
|
|
325
|
-
record.progressObserver?.disconnect();
|
|
326
|
-
registry.delete(el);
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
},
|
|
330
|
-
|
|
331
|
-
init(rootElement = document) {
|
|
332
|
-
const elements = rootElement.querySelectorAll('[data-sa]');
|
|
333
|
-
elements.forEach((el) => {
|
|
334
|
-
const opts = parseDataAttributes(el, config);
|
|
335
|
-
observeElement(el, opts);
|
|
336
|
-
});
|
|
337
|
-
},
|
|
338
|
-
|
|
339
|
-
destroy() {
|
|
340
|
-
registry.forEach((record) => {
|
|
341
|
-
record.observer.disconnect();
|
|
342
|
-
record.progressObserver?.disconnect();
|
|
343
|
-
});
|
|
344
|
-
registry.clear();
|
|
345
|
-
},
|
|
346
|
-
|
|
347
|
-
refresh() {
|
|
348
|
-
const entries = Array.from(registry.entries());
|
|
349
|
-
instance.destroy();
|
|
350
|
-
entries.forEach(([el, record]) => {
|
|
351
|
-
observeElement(el, record.options);
|
|
352
|
-
});
|
|
353
|
-
},
|
|
354
|
-
|
|
355
|
-
animate(target, options = {}) {
|
|
356
|
-
const elements = resolveTargets(target);
|
|
357
|
-
const opts = mergeOptions(options, config);
|
|
358
|
-
elements.forEach((el) => {
|
|
359
|
-
runAnimation(el, opts, config, 0);
|
|
360
|
-
});
|
|
361
|
-
},
|
|
362
|
-
|
|
363
|
-
getObservedElements() {
|
|
364
|
-
return Array.from(registry.values());
|
|
365
|
-
},
|
|
366
|
-
|
|
367
|
-
configure(newConfig) {
|
|
368
|
-
config = { ...config, ...newConfig };
|
|
369
|
-
},
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
return instance;
|
|
373
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* use-scroll-animate
|
|
3
|
-
*
|
|
4
|
-
* A lightweight (~4KB gzipped), dependency-free scroll animation library
|
|
5
|
-
* for modern web applications. Built with TypeScript, powered by
|
|
6
|
-
* IntersectionObserver and the Web Animations API.
|
|
7
|
-
*
|
|
8
|
-
* @version 1.0.0
|
|
9
|
-
* @license MIT
|
|
10
|
-
* @see https://github.com/use-scroll-animate/use-scroll-animate
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
export { createScrollAnimate } from './core';
|
|
14
|
-
export { PRESETS, resolvePreset, resolveEasing, EASING_MAP } from './presets';
|
|
15
|
-
export { createReactHooks } from './react';
|
|
16
|
-
export { createVueComposables } from './vue';
|
|
17
|
-
|
|
18
|
-
export type {
|
|
19
|
-
AnimationPreset,
|
|
20
|
-
EasingType,
|
|
21
|
-
AnimationKeyframe,
|
|
22
|
-
CustomAnimation,
|
|
23
|
-
AnimateOptions,
|
|
24
|
-
ScrollAnimateConfig,
|
|
25
|
-
AnimatedElement,
|
|
26
|
-
ScrollAnimateInstance,
|
|
27
|
-
} from './types';
|
|
28
|
-
|
|
29
|
-
// Default export: a ready-to-use singleton instance
|
|
30
|
-
import { createScrollAnimate } from './core';
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Default singleton instance of ScrollAnimate.
|
|
34
|
-
* Ready to use out of the box with sensible defaults.
|
|
35
|
-
*
|
|
36
|
-
* @example
|
|
37
|
-
* ```js
|
|
38
|
-
* import ScrollAnimate from 'use-scroll-animate';
|
|
39
|
-
*
|
|
40
|
-
* // Auto-initialize all elements with data-sa attribute
|
|
41
|
-
* ScrollAnimate.init();
|
|
42
|
-
*
|
|
43
|
-
* // Or manually observe elements
|
|
44
|
-
* ScrollAnimate.observe('.my-element', { animation: 'fade-in-up' });
|
|
45
|
-
* ```
|
|
46
|
-
*/
|
|
47
|
-
const ScrollAnimate = createScrollAnimate();
|
|
48
|
-
export default ScrollAnimate;
|