@sc4rfurryx/proteusjs 1.1.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +684 -899
- package/dist/.tsbuildinfo +1 -1
- package/dist/modules/a11y-audit.d.ts +1 -1
- package/dist/modules/a11y-audit.esm.js +3 -3
- package/dist/modules/a11y-primitives.d.ts +2 -2
- package/dist/modules/a11y-primitives.esm.js +2 -2
- package/dist/modules/a11y-primitives.esm.js.map +1 -1
- package/dist/modules/anchor.d.ts +1 -1
- package/dist/modules/anchor.esm.js +2 -2
- package/dist/modules/container.d.ts +1 -1
- package/dist/modules/container.esm.js +34 -34
- package/dist/modules/container.esm.js.map +1 -1
- package/dist/modules/perf.d.ts +1 -1
- package/dist/modules/perf.esm.js +2 -2
- package/dist/modules/popover.d.ts +1 -1
- package/dist/modules/popover.esm.js +2 -2
- package/dist/modules/scroll.d.ts +1 -1
- package/dist/modules/scroll.esm.js +14 -14
- package/dist/modules/scroll.esm.js.map +1 -1
- package/dist/modules/transitions.d.ts +1 -1
- package/dist/modules/transitions.esm.js +12 -12
- package/dist/modules/transitions.esm.js.map +1 -1
- package/dist/modules/typography.d.ts +1 -1
- package/dist/modules/typography.esm.js +2 -2
- package/dist/proteus.cjs.js +68 -68
- package/dist/proteus.cjs.js.map +1 -1
- package/dist/proteus.d.ts +13 -13
- package/dist/proteus.esm.js +68 -68
- package/dist/proteus.esm.js.map +1 -1
- package/dist/proteus.esm.min.js +2 -2
- package/dist/proteus.esm.min.js.map +1 -1
- package/dist/proteus.js +68 -68
- package/dist/proteus.js.map +1 -1
- package/dist/proteus.min.js +2 -2
- package/dist/proteus.min.js.map +1 -1
- package/package.json +40 -8
- package/src/adapters/react.ts +607 -264
- package/src/adapters/svelte.ts +321 -321
- package/src/adapters/vue.ts +268 -268
- package/src/core/ProteusJS.ts +6 -6
- package/src/index.ts +2 -2
- package/src/modules/a11y-audit/index.ts +84 -84
- package/src/modules/a11y-primitives/index.ts +151 -151
- package/src/modules/anchor/index.ts +259 -259
- package/src/modules/container/index.ts +230 -230
- package/src/modules/perf/index.ts +291 -291
- package/src/modules/popover/index.ts +238 -238
- package/src/modules/scroll/index.ts +251 -251
- package/src/modules/transitions/index.ts +145 -145
- package/src/modules/typography/index.ts +239 -239
- package/src/utils/version.ts +1 -1
- package/dist/adapters/react.d.ts +0 -140
- package/dist/adapters/react.esm.js +0 -849
- package/dist/adapters/react.esm.js.map +0 -1
- package/dist/adapters/svelte.d.ts +0 -181
- package/dist/adapters/svelte.esm.js +0 -909
- package/dist/adapters/svelte.esm.js.map +0 -1
- package/dist/adapters/vue.d.ts +0 -205
- package/dist/adapters/vue.esm.js +0 -873
- package/dist/adapters/vue.esm.js.map +0 -1
|
@@ -1,251 +1,251 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @sc4rfurryx/proteusjs/scroll
|
|
3
|
-
* Scroll-driven animations with CSS Scroll-Linked Animations
|
|
4
|
-
*
|
|
5
|
-
* @version
|
|
6
|
-
* @author sc4rfurry
|
|
7
|
-
* @license MIT
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
export interface ScrollAnimateOptions {
|
|
11
|
-
keyframes: Keyframe[];
|
|
12
|
-
range?: [string, string];
|
|
13
|
-
timeline?: {
|
|
14
|
-
axis?: 'block' | 'inline';
|
|
15
|
-
start?: string;
|
|
16
|
-
end?: string;
|
|
17
|
-
};
|
|
18
|
-
fallback?: 'io' | false;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Zero-boilerplate setup for CSS Scroll-Linked Animations with fallbacks
|
|
23
|
-
*/
|
|
24
|
-
export function scrollAnimate(
|
|
25
|
-
target: Element | string,
|
|
26
|
-
opts: ScrollAnimateOptions
|
|
27
|
-
): void {
|
|
28
|
-
const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
|
|
29
|
-
if (!targetEl) {
|
|
30
|
-
throw new Error('Target element not found');
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const {
|
|
34
|
-
keyframes,
|
|
35
|
-
range = ['0%', '100%'],
|
|
36
|
-
timeline = {},
|
|
37
|
-
fallback = 'io'
|
|
38
|
-
} = opts;
|
|
39
|
-
|
|
40
|
-
const {
|
|
41
|
-
axis = 'block',
|
|
42
|
-
start = '0%',
|
|
43
|
-
end = '100%'
|
|
44
|
-
} = timeline;
|
|
45
|
-
|
|
46
|
-
// Check for CSS Scroll-Linked Animations support
|
|
47
|
-
const hasScrollTimeline = 'CSS' in window && CSS.supports('animation-timeline', 'scroll()');
|
|
48
|
-
|
|
49
|
-
// Check for reduced motion preference
|
|
50
|
-
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
51
|
-
|
|
52
|
-
if (prefersReducedMotion) {
|
|
53
|
-
// Respect user preference - either disable or reduce animation
|
|
54
|
-
if (fallback === false) return;
|
|
55
|
-
|
|
56
|
-
// Apply only the end state for reduced motion
|
|
57
|
-
const endKeyframe = keyframes[keyframes.length - 1];
|
|
58
|
-
Object.assign((targetEl as HTMLElement).style, endKeyframe);
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (hasScrollTimeline) {
|
|
63
|
-
// Use native CSS Scroll-Linked Animations
|
|
64
|
-
const timelineName = `scroll-timeline-${Math.random().toString(36).substr(2, 9)}`;
|
|
65
|
-
|
|
66
|
-
// Create scroll timeline
|
|
67
|
-
const style = document.createElement('style');
|
|
68
|
-
style.textContent = `
|
|
69
|
-
@scroll-timeline ${timelineName} {
|
|
70
|
-
source: nearest;
|
|
71
|
-
orientation: ${axis};
|
|
72
|
-
scroll-offsets: ${start}, ${end};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
.scroll-animate-${timelineName} {
|
|
76
|
-
animation-timeline: ${timelineName};
|
|
77
|
-
animation-duration: 1ms; /* Required but ignored */
|
|
78
|
-
animation-fill-mode: both;
|
|
79
|
-
}
|
|
80
|
-
`;
|
|
81
|
-
document.head.appendChild(style);
|
|
82
|
-
|
|
83
|
-
// Apply animation class
|
|
84
|
-
targetEl.classList.add(`scroll-animate-${timelineName}`);
|
|
85
|
-
|
|
86
|
-
// Create Web Animations API animation
|
|
87
|
-
const animation = targetEl.animate(keyframes, {
|
|
88
|
-
duration: 1, // Required but ignored with scroll timeline
|
|
89
|
-
fill: 'both'
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// Set scroll timeline (when supported)
|
|
93
|
-
if ('timeline' in animation) {
|
|
94
|
-
(animation as any).timeline = new (window as any).ScrollTimeline({
|
|
95
|
-
source: document.scrollingElement,
|
|
96
|
-
orientation: axis,
|
|
97
|
-
scrollOffsets: [
|
|
98
|
-
{ target: targetEl, edge: 'start', threshold: parseFloat(start) / 100 },
|
|
99
|
-
{ target: targetEl, edge: 'end', threshold: parseFloat(end) / 100 }
|
|
100
|
-
]
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
} else if (fallback === 'io') {
|
|
105
|
-
// Fallback using Intersection Observer
|
|
106
|
-
let animation: Animation | null = null;
|
|
107
|
-
|
|
108
|
-
const observer = new IntersectionObserver(
|
|
109
|
-
(entries) => {
|
|
110
|
-
entries.forEach(entry => {
|
|
111
|
-
const progress = Math.max(0, Math.min(1, entry.intersectionRatio));
|
|
112
|
-
|
|
113
|
-
if (!animation) {
|
|
114
|
-
animation = targetEl.animate(keyframes, {
|
|
115
|
-
duration: 1000,
|
|
116
|
-
fill: 'both'
|
|
117
|
-
});
|
|
118
|
-
animation.pause();
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Update animation progress based on intersection
|
|
122
|
-
animation.currentTime = progress * 1000;
|
|
123
|
-
});
|
|
124
|
-
},
|
|
125
|
-
{
|
|
126
|
-
threshold: Array.from({ length: 101 }, (_, i) => i / 100) // 0 to 1 in 0.01 steps
|
|
127
|
-
}
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
observer.observe(targetEl);
|
|
131
|
-
|
|
132
|
-
// Store cleanup function
|
|
133
|
-
(targetEl as any)._scrollAnimateCleanup = () => {
|
|
134
|
-
observer.disconnect();
|
|
135
|
-
if (animation) {
|
|
136
|
-
animation.cancel();
|
|
137
|
-
}
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Create a scroll-triggered animation that plays once when element enters viewport
|
|
144
|
-
*/
|
|
145
|
-
export function scrollTrigger(
|
|
146
|
-
target: Element | string,
|
|
147
|
-
keyframes: Keyframe[],
|
|
148
|
-
options: KeyframeAnimationOptions = {}
|
|
149
|
-
): void {
|
|
150
|
-
const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
|
|
151
|
-
if (!targetEl) {
|
|
152
|
-
throw new Error('Target element not found');
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Check for reduced motion preference
|
|
156
|
-
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
157
|
-
|
|
158
|
-
if (prefersReducedMotion) {
|
|
159
|
-
// Apply end state immediately
|
|
160
|
-
const endKeyframe = keyframes[keyframes.length - 1];
|
|
161
|
-
Object.assign((targetEl as HTMLElement).style, endKeyframe);
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const observer = new IntersectionObserver(
|
|
166
|
-
(entries) => {
|
|
167
|
-
entries.forEach(entry => {
|
|
168
|
-
if (entry.isIntersecting) {
|
|
169
|
-
// Play animation
|
|
170
|
-
targetEl.animate(keyframes, {
|
|
171
|
-
duration: 600,
|
|
172
|
-
easing: 'ease-out',
|
|
173
|
-
fill: 'forwards',
|
|
174
|
-
...options
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
// Disconnect observer after first trigger
|
|
178
|
-
observer.disconnect();
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
},
|
|
182
|
-
{
|
|
183
|
-
threshold: 0.1,
|
|
184
|
-
rootMargin: '0px 0px -10% 0px'
|
|
185
|
-
}
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
observer.observe(targetEl);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Parallax effect using scroll-driven animations
|
|
193
|
-
*/
|
|
194
|
-
export function parallax(
|
|
195
|
-
target: Element | string,
|
|
196
|
-
speed: number = 0.5
|
|
197
|
-
): void {
|
|
198
|
-
const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
|
|
199
|
-
if (!targetEl) {
|
|
200
|
-
throw new Error('Target element not found');
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Check for reduced motion preference
|
|
204
|
-
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
205
|
-
if (prefersReducedMotion) return;
|
|
206
|
-
|
|
207
|
-
const keyframes = [
|
|
208
|
-
{ transform: `translateY(${-100 * speed}px)` },
|
|
209
|
-
{ transform: `translateY(${100 * speed}px)` }
|
|
210
|
-
];
|
|
211
|
-
|
|
212
|
-
scrollAnimate(targetEl, {
|
|
213
|
-
keyframes,
|
|
214
|
-
range: ['0%', '100%'],
|
|
215
|
-
timeline: { axis: 'block' },
|
|
216
|
-
fallback: 'io'
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Cleanup function to remove scroll animations
|
|
222
|
-
*/
|
|
223
|
-
export function cleanup(target: Element | string): void {
|
|
224
|
-
const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
|
|
225
|
-
if (!targetEl) return;
|
|
226
|
-
|
|
227
|
-
// Call stored cleanup function if it exists
|
|
228
|
-
if ((targetEl as any)._scrollAnimateCleanup) {
|
|
229
|
-
(targetEl as any)._scrollAnimateCleanup();
|
|
230
|
-
delete (targetEl as any)._scrollAnimateCleanup;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Remove animation classes
|
|
234
|
-
targetEl.classList.forEach(className => {
|
|
235
|
-
if (className.startsWith('scroll-animate-')) {
|
|
236
|
-
targetEl.classList.remove(className);
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
// Cancel any running animations
|
|
241
|
-
const animations = targetEl.getAnimations();
|
|
242
|
-
animations.forEach(animation => animation.cancel());
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Export default object for convenience
|
|
246
|
-
export default {
|
|
247
|
-
scrollAnimate,
|
|
248
|
-
scrollTrigger,
|
|
249
|
-
parallax,
|
|
250
|
-
cleanup
|
|
251
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* @sc4rfurryx/proteusjs/scroll
|
|
3
|
+
* Scroll-driven animations with CSS Scroll-Linked Animations
|
|
4
|
+
*
|
|
5
|
+
* @version 2.0.0
|
|
6
|
+
* @author sc4rfurry
|
|
7
|
+
* @license MIT
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface ScrollAnimateOptions {
|
|
11
|
+
keyframes: Keyframe[];
|
|
12
|
+
range?: [string, string];
|
|
13
|
+
timeline?: {
|
|
14
|
+
axis?: 'block' | 'inline';
|
|
15
|
+
start?: string;
|
|
16
|
+
end?: string;
|
|
17
|
+
};
|
|
18
|
+
fallback?: 'io' | false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Zero-boilerplate setup for CSS Scroll-Linked Animations with fallbacks
|
|
23
|
+
*/
|
|
24
|
+
export function scrollAnimate(
|
|
25
|
+
target: Element | string,
|
|
26
|
+
opts: ScrollAnimateOptions
|
|
27
|
+
): void {
|
|
28
|
+
const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
|
|
29
|
+
if (!targetEl) {
|
|
30
|
+
throw new Error('Target element not found');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const {
|
|
34
|
+
keyframes,
|
|
35
|
+
range = ['0%', '100%'],
|
|
36
|
+
timeline = {},
|
|
37
|
+
fallback = 'io'
|
|
38
|
+
} = opts;
|
|
39
|
+
|
|
40
|
+
const {
|
|
41
|
+
axis = 'block',
|
|
42
|
+
start = '0%',
|
|
43
|
+
end = '100%'
|
|
44
|
+
} = timeline;
|
|
45
|
+
|
|
46
|
+
// Check for CSS Scroll-Linked Animations support
|
|
47
|
+
const hasScrollTimeline = 'CSS' in window && CSS.supports('animation-timeline', 'scroll()');
|
|
48
|
+
|
|
49
|
+
// Check for reduced motion preference
|
|
50
|
+
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
51
|
+
|
|
52
|
+
if (prefersReducedMotion) {
|
|
53
|
+
// Respect user preference - either disable or reduce animation
|
|
54
|
+
if (fallback === false) return;
|
|
55
|
+
|
|
56
|
+
// Apply only the end state for reduced motion
|
|
57
|
+
const endKeyframe = keyframes[keyframes.length - 1];
|
|
58
|
+
Object.assign((targetEl as HTMLElement).style, endKeyframe);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (hasScrollTimeline) {
|
|
63
|
+
// Use native CSS Scroll-Linked Animations
|
|
64
|
+
const timelineName = `scroll-timeline-${Math.random().toString(36).substr(2, 9)}`;
|
|
65
|
+
|
|
66
|
+
// Create scroll timeline
|
|
67
|
+
const style = document.createElement('style');
|
|
68
|
+
style.textContent = `
|
|
69
|
+
@scroll-timeline ${timelineName} {
|
|
70
|
+
source: nearest;
|
|
71
|
+
orientation: ${axis};
|
|
72
|
+
scroll-offsets: ${start}, ${end};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.scroll-animate-${timelineName} {
|
|
76
|
+
animation-timeline: ${timelineName};
|
|
77
|
+
animation-duration: 1ms; /* Required but ignored */
|
|
78
|
+
animation-fill-mode: both;
|
|
79
|
+
}
|
|
80
|
+
`;
|
|
81
|
+
document.head.appendChild(style);
|
|
82
|
+
|
|
83
|
+
// Apply animation class
|
|
84
|
+
targetEl.classList.add(`scroll-animate-${timelineName}`);
|
|
85
|
+
|
|
86
|
+
// Create Web Animations API animation
|
|
87
|
+
const animation = targetEl.animate(keyframes, {
|
|
88
|
+
duration: 1, // Required but ignored with scroll timeline
|
|
89
|
+
fill: 'both'
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Set scroll timeline (when supported)
|
|
93
|
+
if ('timeline' in animation) {
|
|
94
|
+
(animation as any).timeline = new (window as any).ScrollTimeline({
|
|
95
|
+
source: document.scrollingElement,
|
|
96
|
+
orientation: axis,
|
|
97
|
+
scrollOffsets: [
|
|
98
|
+
{ target: targetEl, edge: 'start', threshold: parseFloat(start) / 100 },
|
|
99
|
+
{ target: targetEl, edge: 'end', threshold: parseFloat(end) / 100 }
|
|
100
|
+
]
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
} else if (fallback === 'io') {
|
|
105
|
+
// Fallback using Intersection Observer
|
|
106
|
+
let animation: Animation | null = null;
|
|
107
|
+
|
|
108
|
+
const observer = new IntersectionObserver(
|
|
109
|
+
(entries) => {
|
|
110
|
+
entries.forEach(entry => {
|
|
111
|
+
const progress = Math.max(0, Math.min(1, entry.intersectionRatio));
|
|
112
|
+
|
|
113
|
+
if (!animation) {
|
|
114
|
+
animation = targetEl.animate(keyframes, {
|
|
115
|
+
duration: 1000,
|
|
116
|
+
fill: 'both'
|
|
117
|
+
});
|
|
118
|
+
animation.pause();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Update animation progress based on intersection
|
|
122
|
+
animation.currentTime = progress * 1000;
|
|
123
|
+
});
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
threshold: Array.from({ length: 101 }, (_, i) => i / 100) // 0 to 1 in 0.01 steps
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
observer.observe(targetEl);
|
|
131
|
+
|
|
132
|
+
// Store cleanup function
|
|
133
|
+
(targetEl as any)._scrollAnimateCleanup = () => {
|
|
134
|
+
observer.disconnect();
|
|
135
|
+
if (animation) {
|
|
136
|
+
animation.cancel();
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Create a scroll-triggered animation that plays once when element enters viewport
|
|
144
|
+
*/
|
|
145
|
+
export function scrollTrigger(
|
|
146
|
+
target: Element | string,
|
|
147
|
+
keyframes: Keyframe[],
|
|
148
|
+
options: KeyframeAnimationOptions = {}
|
|
149
|
+
): void {
|
|
150
|
+
const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
|
|
151
|
+
if (!targetEl) {
|
|
152
|
+
throw new Error('Target element not found');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Check for reduced motion preference
|
|
156
|
+
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
157
|
+
|
|
158
|
+
if (prefersReducedMotion) {
|
|
159
|
+
// Apply end state immediately
|
|
160
|
+
const endKeyframe = keyframes[keyframes.length - 1];
|
|
161
|
+
Object.assign((targetEl as HTMLElement).style, endKeyframe);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const observer = new IntersectionObserver(
|
|
166
|
+
(entries) => {
|
|
167
|
+
entries.forEach(entry => {
|
|
168
|
+
if (entry.isIntersecting) {
|
|
169
|
+
// Play animation
|
|
170
|
+
targetEl.animate(keyframes, {
|
|
171
|
+
duration: 600,
|
|
172
|
+
easing: 'ease-out',
|
|
173
|
+
fill: 'forwards',
|
|
174
|
+
...options
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Disconnect observer after first trigger
|
|
178
|
+
observer.disconnect();
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
threshold: 0.1,
|
|
184
|
+
rootMargin: '0px 0px -10% 0px'
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
observer.observe(targetEl);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Parallax effect using scroll-driven animations
|
|
193
|
+
*/
|
|
194
|
+
export function parallax(
|
|
195
|
+
target: Element | string,
|
|
196
|
+
speed: number = 0.5
|
|
197
|
+
): void {
|
|
198
|
+
const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
|
|
199
|
+
if (!targetEl) {
|
|
200
|
+
throw new Error('Target element not found');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Check for reduced motion preference
|
|
204
|
+
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
205
|
+
if (prefersReducedMotion) return;
|
|
206
|
+
|
|
207
|
+
const keyframes = [
|
|
208
|
+
{ transform: `translateY(${-100 * speed}px)` },
|
|
209
|
+
{ transform: `translateY(${100 * speed}px)` }
|
|
210
|
+
];
|
|
211
|
+
|
|
212
|
+
scrollAnimate(targetEl, {
|
|
213
|
+
keyframes,
|
|
214
|
+
range: ['0%', '100%'],
|
|
215
|
+
timeline: { axis: 'block' },
|
|
216
|
+
fallback: 'io'
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Cleanup function to remove scroll animations
|
|
222
|
+
*/
|
|
223
|
+
export function cleanup(target: Element | string): void {
|
|
224
|
+
const targetEl = typeof target === 'string' ? document.querySelector(target) : target;
|
|
225
|
+
if (!targetEl) return;
|
|
226
|
+
|
|
227
|
+
// Call stored cleanup function if it exists
|
|
228
|
+
if ((targetEl as any)._scrollAnimateCleanup) {
|
|
229
|
+
(targetEl as any)._scrollAnimateCleanup();
|
|
230
|
+
delete (targetEl as any)._scrollAnimateCleanup;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Remove animation classes
|
|
234
|
+
targetEl.classList.forEach(className => {
|
|
235
|
+
if (className.startsWith('scroll-animate-')) {
|
|
236
|
+
targetEl.classList.remove(className);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// Cancel any running animations
|
|
241
|
+
const animations = targetEl.getAnimations();
|
|
242
|
+
animations.forEach(animation => animation.cancel());
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Export default object for convenience
|
|
246
|
+
export default {
|
|
247
|
+
scrollAnimate,
|
|
248
|
+
scrollTrigger,
|
|
249
|
+
parallax,
|
|
250
|
+
cleanup
|
|
251
|
+
};
|