react-gsap-aos 1.1.1 → 1.1.3

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 CHANGED
@@ -1,36 +1,65 @@
1
1
  # react-gsap-aos
2
2
 
3
- 輕量的 GSAP + ScrollTrigger 整合,用法類似 AOS,專為 React / Next.js 設計。
3
+ [中文文檔](README.zh-TW.md) | English
4
4
 
5
- 動畫樣式參考: [AOS](https://github.com/michalsnik/aos)
5
+ A lightweight GSAP + ScrollTrigger integration with an AOS-like API, specifically designed for React and Next.js applications.
6
6
 
7
- ## 功能
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)
8
9
 
9
- - 基於 GSAP + ScrollTrigger 的捲動觸發動畫
10
- - API 模仿 AOS,易於 React / Next.js 使用
11
- - 支援 data-aos 屬性與函式轉換
12
- - 可在多個區塊平行使用,避免互相干擾
13
- - TypeScript 完整支援,包含動畫/緩動/錨點型別
14
- - 輕量且 SSR 友善,具備卸載清理機制
10
+ [Live Demo](https://react-gsap-aos-nextjs.vercel.app) | [GitHub](https://github.com/GaiaYang/react-gsap-aos)
15
11
 
16
- ## 安裝
12
+ ## What is react-gsap-aos?
17
13
 
18
- ```bash
19
- pnpm install react-gsap-aos gsap @gsap/react
20
- ```
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
21
23
 
22
- 參考:
24
+ While AOS is great for vanilla JavaScript, integrating it with React can be problematic:
23
25
 
24
- - [gsap](https://greensock.com/gsap)
25
- - [@gsap/react](https://greensock.com/scrolltrigger)
26
+ - Manual initialization and cleanup required
27
+ - Not SSR-friendly
28
+ - Limited TypeScript support
29
+ - Difficult to use with dynamic content
26
30
 
27
- ### 版本需求
31
+ `react-gsap-aos` solves these issues by providing a React-native solution that automatically handles DOM mutations, component lifecycle, and SSR scenarios.
28
32
 
29
- - react >= 17
30
- - gsap ^3.12.5
31
- - @gsap/react ^2.1.2
33
+ ## Features
32
34
 
33
- ## 快速開始
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
34
63
 
35
64
  ```tsx
36
65
  import { AOSProvider } from "react-gsap-aos/client";
@@ -48,156 +77,146 @@ export default function Demo() {
48
77
  }
49
78
  ```
50
79
 
51
- ## 用法
52
-
53
- ### 設置 `AOSProvider`
80
+ ## Usage
54
81
 
55
- 只要把 `AOSProvider` 包覆在需要動畫的區塊外層,
56
- 元件內的所有子元素就會自動被監聽與啟動動畫。
82
+ ### Setting up AOSProvider
57
83
 
58
- > `"overflow-hidden"` 樣式是解決動畫元素起始階段溢出問題。
84
+ Wrap your animated content with `AOSProvider`. All child elements with `data-aos` attributes will be automatically animated.
59
85
 
60
86
  ```tsx
61
87
  import { AOSProvider } from "react-gsap-aos/client";
62
88
 
63
89
  export default function Demo() {
64
90
  return (
65
- <AOSProvider className="overflow-hidden">{/* 動畫區塊 */}</AOSProvider>
66
- );
67
- }
68
- ```
69
-
70
- > ⚠️ 不要巢狀使用 `AOSProvider`,會造成重複監聽與多餘動畫。
71
-
72
- ```tsx
73
- import { AOSProvider } from "react-gsap-aos/client";
74
-
75
- export default function Demo() {
76
- return (
77
- <AOSProvider>
78
- {/* ❌ 會導致重複監聽元素以及創建動畫 */}
79
- <AOSProvider />
91
+ <AOSProvider className="overflow-hidden">
92
+ {/* Your animated content */}
80
93
  </AOSProvider>
81
94
  );
82
95
  }
83
96
  ```
84
97
 
85
- `AOSProvider` 是呼叫 `useAOSScope` hook 的包裝元件,大部分情況下使用此組件即可;
98
+ > The `overflow-hidden` class prevents elements from overflowing during their initial animation state.
86
99
 
87
- 如果你需要更細緻的控制,可以改用 [`useAOSScope`](#useaosscope)。
100
+ ⚠️ **Important**: Do not nest `AOSProvider` components, as this will cause duplicate listeners and animations.
88
101
 
89
- 此外該組件提供了兩個屬性供調整:
102
+ ### Configuring Animations with Data Attributes
90
103
 
91
- - `component`:渲染的容器元素,預設為 `'div'`。
92
- - `options`:與 `toAOSProps` 同樣結構的默認動畫參數,會應用於該範圍內的所有新動畫元素。
93
-
94
- ```tsx
95
- <AOSProvider
96
- component="section"
97
- options={{
98
- animation: "fade",
99
- offset: 120,
100
- delay: 0,
101
- duration: 400,
102
- easing: "none",
103
- once: false,
104
- mirror: false,
105
- anchorPlacement: "top-bottom",
106
- }}
107
- ></AOSProvider>
108
- ```
109
-
110
- ### 使用 data attributes 設定動畫
111
-
112
- 透過帶有 `data-aos-*` 開頭的屬性來調整行為
104
+ Use `data-aos-*` attributes to configure animation behavior:
113
105
 
114
106
  ```tsx
115
107
  <div
116
- data-aos="fade"
108
+ data-aos="fade-up"
117
109
  data-aos-offset={120}
118
110
  data-aos-delay={0}
119
111
  data-aos-duration={400}
120
- data-aos-easing="none"
112
+ data-aos-easing="ease-out-cubic"
121
113
  data-aos-mirror={false}
122
114
  data-aos-once={false}
123
115
  data-aos-anchor-placement="top-bottom"
124
116
  >
125
- Hello AOS
117
+ Animated content
126
118
  </div>
127
119
  ```
128
120
 
129
- ### 使用 `toAOSProps` 設定動畫
121
+ ### Using toAOSProps Helper
130
122
 
131
- 該函式提供完整型別且會過濾無效的屬性值後轉換成 data attributes
123
+ For better TypeScript support and validation, use the `toAOSProps` helper:
132
124
 
133
125
  ```tsx
126
+ import { toAOSProps } from "react-gsap-aos";
127
+
134
128
  <div
135
129
  {...toAOSProps({
136
- animation: "fade",
130
+ animation: "fade-up",
137
131
  offset: 120,
138
132
  delay: 0,
139
133
  duration: 400,
140
- easing: "none",
134
+ easing: "power2.out",
141
135
  once: false,
142
136
  mirror: false,
143
137
  anchorPlacement: "top-bottom",
144
138
  })}
145
139
  >
146
- Hello AOS
147
- </div>
140
+ Animated content
141
+ </div>;
148
142
  ```
149
143
 
150
- ### 容器定位 data-aos-container
144
+ ### Container Positioning with data-aos-container
151
145
 
152
- 為了避免 ScrollTrigger 計算偏移時造成觸發點不正確,請在需要的父容器加上 `data-aos-container`:
146
+ To ensure accurate ScrollTrigger calculations, mark parent containers with `data-aos-container`:
153
147
 
154
148
  ```tsx
155
- return (
156
- <AOSProvider className="overflow-hidden">
157
- {/* ✅ 指定定位容器 */}
158
- <div data-aos-container>
159
- <div data-aos="fade-up" data-aos-offset="200">
160
- Hello AOS
161
- </div>
162
- </div>
163
-
164
- {/* ❌ 未指定容器,可能導致觸發點偏移 */}
149
+ <AOSProvider className="overflow-hidden">
150
+ {/* ✅ Correct: Container specified */}
151
+ <div data-aos-container>
165
152
  <div data-aos="fade-up" data-aos-offset="200">
166
153
  Hello AOS
167
154
  </div>
168
- </AOSProvider>
169
- );
170
- ```
155
+ </div>
171
156
 
172
- `react-gsap-aos` 會先往上尋找帶有 `data-aos-container` 的最近父容器,若找不到則使用動畫元素本身作為錨點。
157
+ {/* Incorrect: May cause offset issues */}
158
+ <div data-aos="fade-up" data-aos-offset="200">
159
+ Hello AOS
160
+ </div>
161
+ </AOSProvider>
162
+ ```
173
163
 
174
- 支援嵌套容器:
164
+ Nested containers are supported:
175
165
 
176
166
  ```tsx
177
167
  <div data-aos-container>
178
- <div data-aos="fade-up" data-aos-offset="200">
179
- Hello AOS
180
- </div>
168
+ <div data-aos="fade-up">Parent animation</div>
169
+
181
170
  <div data-aos-container>
182
- <div data-aos="fade-up" data-aos-offset="200">
183
- Nested AOS
184
- </div>
171
+ <div data-aos="zoom-in">Nested animation</div>
185
172
  </div>
186
173
  </div>
187
174
  ```
188
175
 
189
- ## API
176
+ ## API Reference
177
+
178
+ ### AOSProvider
190
179
 
191
- ### `useAOSScope`
180
+ A wrapper component that provides animation scope for its children.
192
181
 
193
- 這個 hook 是整個套件的核心,負責監聽子元素 `data-aos-*` 屬性來創建、修改動畫並且會在離開頁面時卸載動畫,`AOSProvider` 是它的輕量包裝元件,通常你只需使用 `AOSProvider`。
182
+ **Props:**
194
183
 
195
- 若你想在函式元件中直接掌握 container ref,就可以直接使用此 hook。
184
+ | Prop | Type | Default | Description |
185
+ | ----------- | --------------------------- | ----------- | ------------------------------------------ |
186
+ | `component` | `React.ElementType` | `'div'` | The container element to render |
187
+ | `className` | `string` | `undefined` | CSS classes for the container |
188
+ | `options` | `Partial<AnimationOptions>` | `undefined` | Default animation options for all children |
189
+ | `children` | `React.ReactNode` | - | Child elements |
196
190
 
197
- 1. Next.js / SSR 專案在使用此 hook 的檔案最上方加上 `"use client"`。
198
- 2. 呼叫後把回傳的 `containerRef` 綁定到最外層容器。
191
+ **Example:**
199
192
 
200
- > ⚠️ 建議綁定在頁面或元件的區塊層級,不要放在 `app/layout.tsx`,以便 GSAP 在組件卸載時能正確清理。
193
+ ```tsx
194
+ <AOSProvider
195
+ component="section"
196
+ className="overflow-hidden"
197
+ options={{
198
+ duration: 600,
199
+ easing: "power2.out",
200
+ once: true,
201
+ }}
202
+ >
203
+ {/* Children will inherit these default options */}
204
+ </AOSProvider>
205
+ ```
206
+
207
+ > The default options only affect animations generated subsequently. This is intentional behavior.
208
+
209
+ ### useAOSScope
210
+
211
+ The core hook that powers `AOSProvider`. Use this when you need direct control over the container ref.
212
+
213
+ ```tsx
214
+ function useAOSScope<E extends HTMLElement = HTMLElement>(
215
+ options?: Partial<AnimationOptions>,
216
+ ): { containerRef: React.RefObject<E> };
217
+ ```
218
+
219
+ **Example:**
201
220
 
202
221
  ```tsx
203
222
  "use client";
@@ -205,205 +224,159 @@ return (
205
224
  import { useAOSScope } from "react-gsap-aos/client";
206
225
 
207
226
  export default function Demo() {
208
- const { containerRef } = useAOSScope<HTMLDivElement>();
227
+ const { containerRef } = useAOSScope<HTMLDivElement>({
228
+ easing: "bounce.out",
229
+ duration: 800,
230
+ });
209
231
 
210
232
  return (
211
233
  <div ref={containerRef} className="overflow-hidden">
212
- {/* 動畫區塊 */}
234
+ <div data-aos="fade-up">Animated content</div>
213
235
  </div>
214
236
  );
215
237
  }
216
238
  ```
217
239
 
218
- 不要巢狀呼叫 `useAOSScope`,但是你可以像下面這樣平行使用。
240
+ ⚠️ **Important**:
241
+
242
+ - Do not nest `useAOSScope` calls
243
+ - Use in client components only (add `"use client"` directive)
244
+ - Avoid placing in `app/layout.tsx` for proper cleanup
245
+
246
+ **Parallel Usage:**
219
247
 
220
248
  ```tsx
221
249
  function Demo() {
222
250
  return (
223
251
  <div>
224
- {/* ✅ 平行使用互不干涉 */}
225
- <Box />
226
- <Box2 />
252
+ <Section1 />
253
+ <Section2 />
227
254
  </div>
228
255
  );
229
256
  }
230
257
 
231
- function Box() {
258
+ function Section1() {
232
259
  const { containerRef } = useAOSScope<HTMLDivElement>();
233
- return (
234
- <div ref={containerRef} className="overflow-hidden">
235
-
236
- </div>
237
- );
260
+ return <div ref={containerRef}>...</div>;
238
261
  }
239
262
 
240
- function Box2() {
263
+ function Section2() {
241
264
  const { containerRef } = useAOSScope<HTMLDivElement>();
242
- return (
243
- <div ref={containerRef} className="overflow-hidden">
244
-
245
- </div>
246
- );
265
+ return <div ref={containerRef}>...</div>;
247
266
  }
248
267
  ```
249
268
 
250
- 若要在區塊層級覆蓋預設設定,可傳入選項:
269
+ ### toAOSProps
270
+
271
+ Converts animation options to data attributes with type safety.
251
272
 
252
273
  ```tsx
253
- const { containerRef } = useAOSScope<HTMLDivElement>({
254
- easing: "bounce",
255
- duration: 300,
274
+ import { toAOSProps } from "react-gsap-aos";
275
+
276
+ const props = toAOSProps({
277
+ animation: "fade-up",
278
+ duration: 600,
279
+ easing: "power2.out",
256
280
  });
281
+ // Returns: { "data-aos": "fade-up", "data-aos-duration": 600, ... }
257
282
  ```
258
283
 
259
- > 此設定只會應用於該容器中新產生的動畫元素,不建議頻繁動態變更此項設定。
260
-
261
- ## 刷新動畫位置
284
+ ### refreshAOS
262
285
 
263
- 若動態修改 DOM(插入/移除元素或修改佈局),請手動呼叫:
264
-
265
- ```ts
266
- "use client";
286
+ Manually refresh ScrollTrigger calculations when DOM changes occur.
267
287
 
288
+ ```tsx
268
289
  import { refreshAOS } from "react-gsap-aos";
269
290
 
291
+ // Call after dynamic DOM changes
270
292
  refreshAOS();
271
293
  ```
272
294
 
273
- `refreshAOS()` 會封裝 `ScrollTrigger.refresh(true)`,在大多數情況下可安全使用。
274
-
275
- 詳細說明可參考 [GSAP ScrollTrigger refresh()](<https://gsap.com/docs/v3/Plugins/ScrollTrigger/static.refresh()>)
276
-
277
- ### 動態 DOM 範例
278
-
279
- 以下範例示範在動態增減元素導致佈局變動時,如何呼叫 `refreshAOS()` :
295
+ **Example with Dynamic Content:**
280
296
 
281
297
  ```tsx
282
298
  "use client";
283
299
 
284
300
  import { useState, useEffect } from "react";
285
- import { useAOSScope, refreshAOS } from "react-gsap-aos";
301
+ import { AOSProvider, refreshAOS } from "react-gsap-aos/client";
286
302
 
287
303
  export default function DynamicList() {
288
- const { containerRef } = useAOSScope<HTMLDivElement>();
289
- const [show, setShow] = useState(false);
304
+ const [visible, setVisible] = useState(true);
305
+ const [items, setItems] = useState([1, 2, 3]);
290
306
 
291
307
  useEffect(() => {
292
- // show 變動時,刷新 ScrollTrigger
308
+ // Refresh after visible change
293
309
  refreshAOS();
294
- }, [show]);
310
+ }, [visible]);
295
311
 
296
312
  return (
297
- <div ref={containerRef} className="overflow-hidden">
298
- <button type="button" onClick={() => setShow((e) => !e)}>
299
- switch
300
- </button>
301
- {show ? <div className="h-80" /> : null}
313
+ <AOSProvider className="overflow-hidden">
314
+ <button onClick={() => setVisible((e) => !e)}>switch visible</button>
315
+ // When visible changes, the layout changes.
316
+ {visible ? <div className="h-80" /> : null}
302
317
  <div data-aos-container>
303
- <div data-aos="fade-up">Hello AOS</div>
318
+ <div key={item} data-aos="fade-up">
319
+ Hello AOS
320
+ </div>
304
321
  </div>
305
- </div>
322
+ </AOSProvider>
306
323
  );
307
324
  }
308
325
  ```
309
326
 
310
- ## 型別與選項
311
-
312
- ### 屬性選項
313
-
314
- | 名稱 | 型別 | 對應 `data-aos` | 預設 | 說明 |
315
- | --------------- | ----------------- | --------------------------- | -------------- | ------------------------ |
316
- | animation | `Animation` | `data-aos` | `undefined` | 動畫類型 |
317
- | offset | `number` | `data-aos-offset` | `120` | 提前觸發動畫的距離(px) |
318
- | delay | `number` | `data-aos-delay` | `0` | 動畫延遲(ms) |
319
- | duration | `number` | `data-aos-duration` | `400` | 動畫持續時間(ms) |
320
- | easing | `Easing` | `data-aos-easing` | `"none"` | 緩動曲線 |
321
- | once | `boolean` | `data-aos-once` | `false` | 是否只執行一次 |
322
- | mirror | `boolean` | `data-aos-mirror` | `false` | 是否於離開時反向播放 |
323
- | anchorPlacement | `AnchorPlacement` | `data-aos-anchor-placement` | `"top-bottom"` | 觸發位置設定 |
324
-
325
- ### Animation
326
-
327
- - fade
328
- - fade-up
329
- - fade-down
330
- - fade-left
331
- - fade-right
332
- - fade-up-right
333
- - fade-up-left
334
- - fade-down-right
335
- - fade-down-left
336
- - flip-up
337
- - flip-down
338
- - flip-left
339
- - flip-right
340
- - slide-up
341
- - slide-down
342
- - slide-left
343
- - slide-right
344
- - zoom-in
345
- - zoom-in-up
346
- - zoom-in-down
347
- - zoom-in-left
348
- - zoom-in-right
349
- - zoom-out
350
- - zoom-out-up
351
- - zoom-out-down
352
- - zoom-out-left
353
- - zoom-out-right
354
-
355
- ### Easing
356
-
357
- - none
358
- - power1
359
- - power1.in
360
- - power1.out
361
- - power1.inOut
362
- - power2
363
- - power2.in
364
- - power2.out
365
- - power2.inOut
366
- - power3
367
- - power3.in
368
- - power3.out
369
- - power3.inOut
370
- - power4
371
- - power4.in
372
- - power4.out
373
- - power4.inOut
374
- - back
375
- - back.in
376
- - back.out
377
- - back.inOut
378
- - bounce
379
- - bounce.in
380
- - bounce.out
381
- - bounce.inOut
382
- - circ
383
- - circ.in
384
- - circ.out
385
- - circ.inOut
386
- - elastic
387
- - elastic.in
388
- - elastic.out
389
- - elastic.inOut
390
- - expo
391
- - expo.in
392
- - expo.out
393
- - expo.inOut
394
- - sine
395
- - sine.in
396
- - sine.out
397
- - sine.inOut
398
-
399
- ## AnchorPlacement
400
-
401
- - top-bottom
402
- - top-center
403
- - top-top
404
- - center-bottom
405
- - center-center
406
- - center-top
407
- - bottom-bottom
408
- - bottom-center
409
- - bottom-top
327
+ ## Animation Options
328
+
329
+ | Option | Type | Data Attribute | Default | Description |
330
+ | ----------------- | ----------------- | --------------------------- | -------------- | ------------------------------ |
331
+ | `animation` | `Animation` | `data-aos` | `undefined` | Animation type |
332
+ | `offset` | `number` | `data-aos-offset` | `120` | Offset (px) from trigger point |
333
+ | `delay` | `number` | `data-aos-delay` | `0` | Animation delay (ms) |
334
+ | `duration` | `number` | `data-aos-duration` | `400` | Animation duration (ms) |
335
+ | `easing` | `Easing` | `data-aos-easing` | `"none"` | Easing function |
336
+ | `once` | `boolean` | `data-aos-once` | `false` | Animate only once |
337
+ | `mirror` | `boolean` | `data-aos-mirror` | `false` | Reverse animation on scroll up |
338
+ | `anchorPlacement` | `AnchorPlacement` | `data-aos-anchor-placement` | `"top-bottom"` | Trigger position |
339
+
340
+ ## Available Types
341
+
342
+ ### Animation Types (34 total)
343
+
344
+ **Fade Animations:**
345
+
346
+ - `fade`, `fade-up`, `fade-down`, `fade-left`, `fade-right`
347
+ - `fade-up-right`, `fade-up-left`, `fade-down-right`, `fade-down-left`
348
+
349
+ **Flip Animations:**
350
+
351
+ - `flip-up`, `flip-down`, `flip-left`, `flip-right`
352
+
353
+ **Slide Animations:**
354
+
355
+ - `slide-up`, `slide-down`, `slide-left`, `slide-right`
356
+
357
+ **Zoom Animations:**
358
+
359
+ - `zoom-in`, `zoom-in-up`, `zoom-in-down`, `zoom-in-left`, `zoom-in-right`
360
+ - `zoom-out`, `zoom-out-up`, `zoom-out-down`, `zoom-out-left`, `zoom-out-right`
361
+
362
+ ### Easing Types (17 total)
363
+
364
+ - `none`
365
+ - `power1`, `power1.in`, `power1.out`, `power1.inOut`
366
+ - `power2`, `power2.in`, `power2.out`, `power2.inOut`
367
+ - `power3`, `power3.in`, `power3.out`, `power3.inOut`
368
+ - `power4`, `power4.in`, `power4.out`, `power4.inOut`
369
+ - `back`, `back.in`, `back.out`, `back.inOut`
370
+ - `bounce`, `bounce.in`, `bounce.out`, `bounce.inOut`
371
+ - `circ`, `circ.in`, `circ.out`, `circ.inOut`
372
+ - `elastic`, `elastic.in`, `elastic.out`, `elastic.inOut`
373
+ - `expo`, `expo.in`, `expo.out`, `expo.inOut`
374
+ - `sine`, `sine.in`, `sine.out`, `sine.inOut`
375
+
376
+ ### Anchor Placement Types (9 total)
377
+
378
+ Format: `[element-position]-[viewport-position]`
379
+
380
+ - `top-bottom`, `top-center`, `top-top`
381
+ - `center-bottom`, `center-center`, `center-top`
382
+ - `bottom-bottom`, `bottom-center`, `bottom-top`