react-gsap-aos 1.4.0 → 1.4.2

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.en.md ADDED
@@ -0,0 +1,385 @@
1
+ # react-gsap-aos
2
+
3
+ English | [中文文檔](README.zh-TW.md)
4
+
5
+ A lightweight GSAP + ScrollTrigger integration with an AOS-like API, specifically designed for React and Next.js applications.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/react-gsap-aos.svg)](https://www.npmjs.com/package/react-gsap-aos)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
9
+
10
+ [Live Demo](https://react-gsap-aos-nextjs.vercel.app)
11
+
12
+ ## What is react-gsap-aos?
13
+
14
+ `react-gsap-aos` bridges the gap between GSAP's powerful animation capabilities and the simplicity of AOS (Animate On Scroll). It provides:
15
+
16
+ - **Familiar API**: If you've used AOS, you already know how to use this
17
+ - **GSAP Power**: Built on GSAP + ScrollTrigger for smooth, performant animations
18
+ - **React-First**: Designed specifically for React and Next.js with proper SSR support
19
+ - **TypeScript**: Full type safety for animations, easings, and anchor placements
20
+ - **Automatic Cleanup**: Properly manages animation lifecycle with React's component lifecycle
21
+
22
+ ### Problem It Solves
23
+
24
+ While AOS is great for vanilla JavaScript, integrating it with React can be problematic:
25
+
26
+ - Manual initialization and cleanup required
27
+ - Not SSR-friendly
28
+ - Limited TypeScript support
29
+ - Difficult to use with dynamic content
30
+
31
+ `react-gsap-aos` by React solution that automatically handles DOM mutations, component lifecycle, and SSR scenarios.
32
+
33
+ ## Features
34
+
35
+ - 🎬 Scroll-triggered animations powered by GSAP + ScrollTrigger
36
+ - 🎯 AOS-like API with `data-aos` attributes
37
+ - ⚛️ Built for React / Next.js with SSR support
38
+ - 🔄 Automatic animation management with DOM mutations
39
+ - 📦 Multiple parallel scopes without interference
40
+ - 🎨 34 animation presets (fade, slide, flip, zoom variants)
41
+ - 🎭 17 easing options from GSAP
42
+ - 📍 9 anchor placement options for precise triggering
43
+ - 🧹 Automatic cleanup on component unmount
44
+ - 💪 Full TypeScript support
45
+
46
+ ## Installation
47
+
48
+ ```bash
49
+ npm install react-gsap-aos gsap @gsap/react
50
+ # or
51
+ yarn add react-gsap-aos gsap @gsap/react
52
+ # or
53
+ pnpm add react-gsap-aos gsap @gsap/react
54
+ ```
55
+
56
+ ### Peer Dependencies
57
+
58
+ - `react` >= 17
59
+ - `gsap` ^3.12.5
60
+ - `@gsap/react` ^2.1.2
61
+
62
+ ## Quick Start
63
+
64
+ ```tsx
65
+ import { AOSProvider } from "react-gsap-aos/client";
66
+
67
+ export default function Demo() {
68
+ return (
69
+ <AOSProvider className="overflow-hidden">
70
+ <div data-aos-container>
71
+ <div data-aos="fade-up" data-aos-offset="200">
72
+ Hello AOS
73
+ </div>
74
+ </div>
75
+ </AOSProvider>
76
+ );
77
+ }
78
+ ```
79
+
80
+ ## Usage
81
+
82
+ ### Setting up AOSProvider
83
+
84
+ Wrap your animated content with `AOSProvider`. All child elements with `data-aos` attributes will be automatically animated.
85
+
86
+ ```tsx
87
+ import { AOSProvider } from "react-gsap-aos/client";
88
+
89
+ export default function Demo() {
90
+ return (
91
+ <AOSProvider className="overflow-hidden">
92
+ {/* Your animated content */}
93
+ </AOSProvider>
94
+ );
95
+ }
96
+ ```
97
+
98
+ > The `overflow-hidden` class prevents elements from overflowing during their initial animation state.
99
+
100
+ ⚠️ **Important**: Do not nest `AOSProvider` components, as this will cause duplicate listeners and animations.
101
+
102
+ ### Configuring Animations with Data Attributes
103
+
104
+ Use `data-aos-*` attributes to configure animation behavior:
105
+
106
+ ```tsx
107
+ <div
108
+ data-aos="fade-up"
109
+ data-aos-offset={120}
110
+ data-aos-delay={0}
111
+ data-aos-duration={400}
112
+ data-aos-easing="ease-out-cubic"
113
+ data-aos-mirror={false}
114
+ data-aos-once={false}
115
+ data-aos-anchor-placement="top-bottom"
116
+ >
117
+ Animated content
118
+ </div>
119
+ ```
120
+
121
+ ### Using toAOSProps Helper
122
+
123
+ For better TypeScript support and validation, use the `toAOSProps` helper:
124
+
125
+ ```tsx
126
+ import { toAOSProps } from "react-gsap-aos";
127
+
128
+ <div
129
+ {...toAOSProps({
130
+ animation: "fade-up",
131
+ offset: 120,
132
+ delay: 0,
133
+ duration: 400,
134
+ easing: "power2.out",
135
+ once: false,
136
+ mirror: false,
137
+ anchorPlacement: "top-bottom",
138
+ })}
139
+ >
140
+ Animated content
141
+ </div>;
142
+ ```
143
+
144
+ ### Container Positioning with data-aos-container
145
+
146
+ To ensure accurate ScrollTrigger calculations, mark parent containers with `data-aos-container`:
147
+
148
+ ```tsx
149
+ <AOSProvider className="overflow-hidden">
150
+ {/* ✅ Correct: Container specified */}
151
+ <div data-aos-container>
152
+ <div data-aos="fade-up" data-aos-offset="200">
153
+ Hello AOS
154
+ </div>
155
+ </div>
156
+
157
+ {/* ❌ Incorrect: May cause offset issues */}
158
+ <div data-aos="fade-up" data-aos-offset="200">
159
+ Hello AOS
160
+ </div>
161
+ </AOSProvider>
162
+ ```
163
+
164
+ Nested data-aos-container usage is **not recommended**:
165
+
166
+ ```tsx
167
+ <div data-aos-container>
168
+ <div data-aos="fade-up">Parent animation</div>
169
+
170
+ <div data-aos-container>
171
+ <div data-aos="zoom-in">Nested animation</div>
172
+ </div>
173
+ </div>
174
+ ```
175
+
176
+ Nested containers increase the complexity of animation initialization and ScrollTrigger refresh timing. While animations will still register, users need to manually call `ScrollTrigger.refresh()` at the appropriate time.
177
+
178
+ ## API Reference
179
+
180
+ ### AOSProvider
181
+
182
+ A wrapper component that provides animation scope for its children.
183
+
184
+ **Props:**
185
+
186
+ | Prop | Type | Default | Description |
187
+ | ----------- | --------------------------- | ----------- | ------------------------------------------ |
188
+ | `component` | `React.ElementType` | `'div'` | The container element to render |
189
+ | `className` | `string` | `undefined` | CSS classes for the container |
190
+ | `options` | `Partial<AnimationOptions>` | `undefined` | Default animation options for all children |
191
+ | `children` | `React.ReactNode` | - | Child elements |
192
+
193
+ **Example:**
194
+
195
+ ```tsx
196
+ <AOSProvider
197
+ component="section"
198
+ className="overflow-hidden"
199
+ options={{
200
+ duration: 600,
201
+ easing: "power2.out",
202
+ once: true,
203
+ }}
204
+ >
205
+ {/* Children will inherit these default options */}
206
+ </AOSProvider>
207
+ ```
208
+
209
+ > The default options only affect animations generated subsequently. This is intentional behavior.
210
+
211
+ ### useAOSScope
212
+
213
+ The core hook that powers `AOSProvider`. Use this when you need direct control over the container ref.
214
+
215
+ ```tsx
216
+ function useAOSScope<E extends HTMLElement = HTMLElement>(
217
+ options?: Partial<AnimationOptions>,
218
+ ): { containerRef: React.RefObject<E> };
219
+ ```
220
+
221
+ **Example:**
222
+
223
+ ```tsx
224
+ "use client";
225
+
226
+ import { useAOSScope } from "react-gsap-aos/client";
227
+
228
+ export default function Demo() {
229
+ const { containerRef } = useAOSScope<HTMLDivElement>({
230
+ easing: "bounce.out",
231
+ duration: 800,
232
+ });
233
+
234
+ return (
235
+ <div ref={containerRef} className="overflow-hidden">
236
+ <div data-aos="fade-up">Animated content</div>
237
+ </div>
238
+ );
239
+ }
240
+ ```
241
+
242
+ ⚠️ **Important**:
243
+
244
+ - Do not nest `useAOSScope` calls
245
+ - Use in client components only (add `"use client"` directive)
246
+ - Avoid placing in `app/layout.tsx` for proper cleanup
247
+
248
+ **Parallel Usage:**
249
+
250
+ ```tsx
251
+ function Demo() {
252
+ return (
253
+ <div>
254
+ <Section1 />
255
+ <Section2 />
256
+ </div>
257
+ );
258
+ }
259
+
260
+ function Section1() {
261
+ const { containerRef } = useAOSScope<HTMLDivElement>();
262
+ return <div ref={containerRef}>...</div>;
263
+ }
264
+
265
+ function Section2() {
266
+ const { containerRef } = useAOSScope<HTMLDivElement>();
267
+ return <div ref={containerRef}>...</div>;
268
+ }
269
+ ```
270
+
271
+ ### toAOSProps
272
+
273
+ Converts animation options to data attributes with type safety.
274
+
275
+ ```tsx
276
+ import { toAOSProps } from "react-gsap-aos";
277
+
278
+ const props = toAOSProps({
279
+ animation: "fade-up",
280
+ duration: 600,
281
+ easing: "power2.out",
282
+ });
283
+ // Returns: { "data-aos": "fade-up", "data-aos-duration": 600, ... }
284
+ ```
285
+
286
+ ### refreshScrollTrigger
287
+
288
+ Manually refresh AOS animation positions, wrapper around [`ScrollTrigger.refresh`](<https://gsap.com/docs/v3/Plugins/ScrollTrigger/refresh()>).
289
+
290
+ ```tsx
291
+ import { refreshScrollTrigger } from "react-gsap-aos";
292
+
293
+ // Call after dynamic DOM changes
294
+ refreshScrollTrigger();
295
+ ```
296
+
297
+ **Example with Dynamic Content:**
298
+
299
+ ```tsx
300
+ "use client";
301
+
302
+ import { useState, useEffect } from "react";
303
+ import { AOSProvider, refreshScrollTrigger } from "react-gsap-aos/client";
304
+
305
+ export default function DynamicList() {
306
+ const [visible, setVisible] = useState(true);
307
+ const [items, setItems] = useState([1, 2, 3]);
308
+
309
+ useEffect(() => {
310
+ // Refresh after visible change
311
+ refreshScrollTrigger();
312
+ }, [visible]);
313
+
314
+ return (
315
+ <AOSProvider className="overflow-hidden">
316
+ <button onClick={() => setVisible((e) => !e)}>switch visible</button>
317
+ // When visible changes, the layout changes.
318
+ {visible ? <div className="h-80" /> : null}
319
+ <div data-aos-container>
320
+ <div key={item} data-aos="fade-up">
321
+ Hello AOS
322
+ </div>
323
+ </div>
324
+ </AOSProvider>
325
+ );
326
+ }
327
+ ```
328
+
329
+ ## Animation Options
330
+
331
+ | Option | Type | Data Attribute | Default | Description |
332
+ | ----------------- | ----------------- | --------------------------- | -------------- | ------------------------------ |
333
+ | `animation` | `Animation` | `data-aos` | `undefined` | Animation type |
334
+ | `offset` | `number` | `data-aos-offset` | `120` | Offset (px) from trigger point |
335
+ | `delay` | `number` | `data-aos-delay` | `0` | Animation delay (ms) |
336
+ | `duration` | `number` | `data-aos-duration` | `400` | Animation duration (ms) |
337
+ | `easing` | `Easing` | `data-aos-easing` | `"none"` | Easing function |
338
+ | `once` | `boolean` | `data-aos-once` | `false` | Animate only once |
339
+ | `mirror` | `boolean` | `data-aos-mirror` | `false` | Reverse animation on scroll up |
340
+ | `anchorPlacement` | `AnchorPlacement` | `data-aos-anchor-placement` | `"top-bottom"` | Trigger position |
341
+ | `markers` | `boolean` | `data-aos-markers` | `false` | ScrollTrigger markers |
342
+
343
+ ## Available Types
344
+
345
+ ### Animation Types (34 total)
346
+
347
+ **Fade Animations:**
348
+
349
+ - `fade`, `fade-up`, `fade-down`, `fade-left`, `fade-right`
350
+ - `fade-up-right`, `fade-up-left`, `fade-down-right`, `fade-down-left`
351
+
352
+ **Flip Animations:**
353
+
354
+ - `flip-up`, `flip-down`, `flip-left`, `flip-right`
355
+
356
+ **Slide Animations:**
357
+
358
+ - `slide-up`, `slide-down`, `slide-left`, `slide-right`
359
+
360
+ **Zoom Animations:**
361
+
362
+ - `zoom-in`, `zoom-in-up`, `zoom-in-down`, `zoom-in-left`, `zoom-in-right`
363
+ - `zoom-out`, `zoom-out-up`, `zoom-out-down`, `zoom-out-left`, `zoom-out-right`
364
+
365
+ ### Easing Types (17 total)
366
+
367
+ - `none`
368
+ - `power1`, `power1.in`, `power1.out`, `power1.inOut`
369
+ - `power2`, `power2.in`, `power2.out`, `power2.inOut`
370
+ - `power3`, `power3.in`, `power3.out`, `power3.inOut`
371
+ - `power4`, `power4.in`, `power4.out`, `power4.inOut`
372
+ - `back`, `back.in`, `back.out`, `back.inOut`
373
+ - `bounce`, `bounce.in`, `bounce.out`, `bounce.inOut`
374
+ - `circ`, `circ.in`, `circ.out`, `circ.inOut`
375
+ - `elastic`, `elastic.in`, `elastic.out`, `elastic.inOut`
376
+ - `expo`, `expo.in`, `expo.out`, `expo.inOut`
377
+ - `sine`, `sine.in`, `sine.out`, `sine.inOut`
378
+
379
+ ### Anchor Placement Types (9 total)
380
+
381
+ Format: `[element-position]-[viewport-position]`
382
+
383
+ - `top-bottom`, `top-center`, `top-top`
384
+ - `center-bottom`, `center-center`, `center-top`
385
+ - `bottom-bottom`, `bottom-center`, `bottom-top`