react-ui-animate 5.3.0-next.3 → 5.3.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/LICENSE +21 -21
- package/README.md +743 -743
- package/package.json +73 -73
package/README.md
CHANGED
|
@@ -1,743 +1,743 @@
|
|
|
1
|
-
# React UI Animate
|
|
2
|
-
|
|
3
|
-
[](https://badge.fury.io/js/react-ui-animate)
|
|
4
|
-
|
|
5
|
-
> Create smooth animations and interactive gestures in React applications effortlessly.
|
|
6
|
-
|
|
7
|
-
## Installation
|
|
8
|
-
|
|
9
|
-
```sh
|
|
10
|
-
npm install react-ui-animate
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
```sh
|
|
14
|
-
yarn add react-ui-animate
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## Quick Start
|
|
20
|
-
|
|
21
|
-
The `react-ui-animate` library provides a declarative way to add animations and gestures to your React components. Here's a simple example:
|
|
22
|
-
|
|
23
|
-
```tsx
|
|
24
|
-
import { animate, withSpring } from 'react-ui-animate';
|
|
25
|
-
|
|
26
|
-
function App() {
|
|
27
|
-
return (
|
|
28
|
-
<animate.div
|
|
29
|
-
style={{
|
|
30
|
-
width: 100,
|
|
31
|
-
height: 100,
|
|
32
|
-
backgroundColor: 'blue',
|
|
33
|
-
scale: 0.5,
|
|
34
|
-
opacity: 0,
|
|
35
|
-
}}
|
|
36
|
-
animate={{
|
|
37
|
-
scale: withSpring(1),
|
|
38
|
-
opacity: withSpring(1),
|
|
39
|
-
}}
|
|
40
|
-
/>
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
## Core Concepts
|
|
48
|
-
|
|
49
|
-
### 1. Animate Component
|
|
50
|
-
|
|
51
|
-
The `animate` object provides animated versions of all HTML elements. Use `animate.div`, `animate.button`, `animate.span`, etc., just like regular React elements.
|
|
52
|
-
|
|
53
|
-
#### Basic Animation
|
|
54
|
-
|
|
55
|
-
Use the `animate` prop to define animations that run when the component mounts or when the prop changes:
|
|
56
|
-
|
|
57
|
-
```tsx
|
|
58
|
-
import { animate, withSpring, withTiming } from 'react-ui-animate';
|
|
59
|
-
|
|
60
|
-
<animate.div
|
|
61
|
-
style={{
|
|
62
|
-
width: 100,
|
|
63
|
-
height: 100,
|
|
64
|
-
backgroundColor: 'red',
|
|
65
|
-
translateX: 0,
|
|
66
|
-
}}
|
|
67
|
-
animate={{
|
|
68
|
-
translateX: withSpring(200),
|
|
69
|
-
backgroundColor: withTiming('blue', { duration: 500 }),
|
|
70
|
-
}}
|
|
71
|
-
/>
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
#### Exit Animations with Presence
|
|
75
|
-
|
|
76
|
-
Wrap components in `Presence` to enable exit animations when they're removed:
|
|
77
|
-
|
|
78
|
-
```tsx
|
|
79
|
-
import { Presence, animate, withSpring, withTiming } from 'react-ui-animate';
|
|
80
|
-
|
|
81
|
-
function Modal({ isOpen, onClose }) {
|
|
82
|
-
return (
|
|
83
|
-
<Presence>
|
|
84
|
-
{isOpen && (
|
|
85
|
-
<animate.div
|
|
86
|
-
key="modal"
|
|
87
|
-
style={{
|
|
88
|
-
opacity: 0,
|
|
89
|
-
scale: 0.8,
|
|
90
|
-
}}
|
|
91
|
-
animate={{
|
|
92
|
-
opacity: withTiming(1),
|
|
93
|
-
scale: withSpring(1),
|
|
94
|
-
}}
|
|
95
|
-
exit={{
|
|
96
|
-
opacity: withTiming(0),
|
|
97
|
-
scale: withSpring(0.8),
|
|
98
|
-
}}
|
|
99
|
-
>
|
|
100
|
-
Modal Content
|
|
101
|
-
</animate.div>
|
|
102
|
-
)}
|
|
103
|
-
</Presence>
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
#### State-Based Animations
|
|
109
|
-
|
|
110
|
-
React to user interactions with `hover`, `press`, `focus`, and `view` props:
|
|
111
|
-
|
|
112
|
-
```tsx
|
|
113
|
-
<animate.button
|
|
114
|
-
style={{
|
|
115
|
-
scale: 1,
|
|
116
|
-
backgroundColor: '#3399ff',
|
|
117
|
-
}}
|
|
118
|
-
hover={{
|
|
119
|
-
scale: withSpring(1.05),
|
|
120
|
-
backgroundColor: withTiming('#4da6ff'),
|
|
121
|
-
}}
|
|
122
|
-
press={{
|
|
123
|
-
scale: withSpring(0.95),
|
|
124
|
-
}}
|
|
125
|
-
>
|
|
126
|
-
Hover Me
|
|
127
|
-
</animate.button>
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
**View Animations** - Animate when elements enter the viewport:
|
|
131
|
-
|
|
132
|
-
```tsx
|
|
133
|
-
<animate.div
|
|
134
|
-
style={{
|
|
135
|
-
opacity: 0,
|
|
136
|
-
translateY: 50,
|
|
137
|
-
}}
|
|
138
|
-
view={{
|
|
139
|
-
opacity: withTiming(1),
|
|
140
|
-
translateY: withSpring(0),
|
|
141
|
-
}}
|
|
142
|
-
viewOptions={{ threshold: 0.3, once: true }}
|
|
143
|
-
>
|
|
144
|
-
This animates when scrolled into view
|
|
145
|
-
</animate.div>
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
### 2. useValue Hook
|
|
149
|
-
|
|
150
|
-
Create and control animated values programmatically:
|
|
151
|
-
|
|
152
|
-
```tsx
|
|
153
|
-
import { useValue, animate, withSpring, withSequence, withTiming } from 'react-ui-animate';
|
|
154
|
-
|
|
155
|
-
function AnimatedBox() {
|
|
156
|
-
const [width, setWidth] = useValue(100);
|
|
157
|
-
const [x, setX] = useValue(0);
|
|
158
|
-
|
|
159
|
-
return (
|
|
160
|
-
<>
|
|
161
|
-
<button onClick={() => setWidth(withSpring(200))}>
|
|
162
|
-
Expand
|
|
163
|
-
</button>
|
|
164
|
-
<button onClick={() => setX(withSequence([
|
|
165
|
-
withTiming(100, { duration: 300 }),
|
|
166
|
-
withTiming(200, { duration: 300 }),
|
|
167
|
-
withTiming(0, { duration: 300 }),
|
|
168
|
-
]))}>
|
|
169
|
-
Sequence
|
|
170
|
-
</button>
|
|
171
|
-
|
|
172
|
-
<animate.div
|
|
173
|
-
style={{
|
|
174
|
-
width,
|
|
175
|
-
height: 100,
|
|
176
|
-
backgroundColor: 'red',
|
|
177
|
-
translateX: x,
|
|
178
|
-
}}
|
|
179
|
-
/>
|
|
180
|
-
</>
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
**Interpolation** - Map values to different ranges:
|
|
186
|
-
|
|
187
|
-
```tsx
|
|
188
|
-
const [progress, setProgress] = useValue(0);
|
|
189
|
-
|
|
190
|
-
<animate.div
|
|
191
|
-
style={{
|
|
192
|
-
backgroundColor: progress.to([0, 1], ['red', 'blue']),
|
|
193
|
-
translateX: progress.to([0, 1], [0, 500]),
|
|
194
|
-
}}
|
|
195
|
-
/>
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
**Controls** - Control animations programmatically:
|
|
199
|
-
|
|
200
|
-
```tsx
|
|
201
|
-
const [value, setValue, controls] = useValue(0);
|
|
202
|
-
|
|
203
|
-
// Start animation
|
|
204
|
-
setValue(withSpring(100));
|
|
205
|
-
|
|
206
|
-
// Control playback
|
|
207
|
-
controls.pause();
|
|
208
|
-
controls.resume();
|
|
209
|
-
controls.cancel();
|
|
210
|
-
controls.reset();
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
### 3. Animation Descriptors
|
|
214
|
-
|
|
215
|
-
Animation descriptors define how animations behave. They can be used with both `animate` components and `useValue`.
|
|
216
|
-
|
|
217
|
-
#### withSpring
|
|
218
|
-
|
|
219
|
-
Creates physics-based spring animations:
|
|
220
|
-
|
|
221
|
-
```tsx
|
|
222
|
-
withSpring(100, {
|
|
223
|
-
stiffness: 100, // Spring stiffness (default: 100)
|
|
224
|
-
damping: 15, // Spring damping (default: 15)
|
|
225
|
-
mass: 1, // Spring mass (default: 1)
|
|
226
|
-
})
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
#### withTiming
|
|
230
|
-
|
|
231
|
-
Creates time-based animations with easing:
|
|
232
|
-
|
|
233
|
-
```tsx
|
|
234
|
-
withTiming(100, {
|
|
235
|
-
duration: 500, // Duration in milliseconds
|
|
236
|
-
easing: Easing.easeInOut, // Easing function
|
|
237
|
-
})
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
#### withDecay
|
|
241
|
-
|
|
242
|
-
Creates momentum-based decay animations:
|
|
243
|
-
|
|
244
|
-
```tsx
|
|
245
|
-
withDecay({
|
|
246
|
-
velocity: 100, // Initial velocity
|
|
247
|
-
clamp: [0, 500], // Optional: clamp values
|
|
248
|
-
})
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
#### withSequence
|
|
252
|
-
|
|
253
|
-
Runs animations one after another:
|
|
254
|
-
|
|
255
|
-
```tsx
|
|
256
|
-
withSequence([
|
|
257
|
-
withTiming(100, { duration: 300 }),
|
|
258
|
-
withTiming(200, { duration: 300 }),
|
|
259
|
-
withSpring(0, { stiffness: 200 }),
|
|
260
|
-
])
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
#### withLoop
|
|
264
|
-
|
|
265
|
-
Repeats animations:
|
|
266
|
-
|
|
267
|
-
```tsx
|
|
268
|
-
withLoop(
|
|
269
|
-
withSequence([
|
|
270
|
-
withTiming(90, { duration: 500 }),
|
|
271
|
-
withTiming(180, { duration: 500 }),
|
|
272
|
-
withTiming(360, { duration: 500 }),
|
|
273
|
-
]),
|
|
274
|
-
3 // Number of iterations (0 = infinite)
|
|
275
|
-
)
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
#### withDelay
|
|
279
|
-
|
|
280
|
-
Adds delay to animations (typically used inside `withSequence`):
|
|
281
|
-
|
|
282
|
-
```tsx
|
|
283
|
-
withSequence([
|
|
284
|
-
withDelay(500),
|
|
285
|
-
withTiming(1, { duration: 500 }),
|
|
286
|
-
])
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
### 4. Animation Recipes
|
|
290
|
-
|
|
291
|
-
Pre-built animation recipes for common use cases:
|
|
292
|
-
|
|
293
|
-
```tsx
|
|
294
|
-
import {
|
|
295
|
-
animate,
|
|
296
|
-
fadeIn,
|
|
297
|
-
slideInUp,
|
|
298
|
-
scaleIn,
|
|
299
|
-
bounceIn,
|
|
300
|
-
hoverScale,
|
|
301
|
-
pressScale,
|
|
302
|
-
} from 'react-ui-animate';
|
|
303
|
-
|
|
304
|
-
// Enter animations
|
|
305
|
-
<animate.div animate={fadeIn}>Fades in</animate.div>
|
|
306
|
-
<animate.div animate={slideInUp}>Slides up</animate.div>
|
|
307
|
-
<animate.div animate={scaleIn}>Scales in</animate.div>
|
|
308
|
-
<animate.div animate={bounceIn}>Bounces in</animate.div>
|
|
309
|
-
|
|
310
|
-
// State animations
|
|
311
|
-
<animate.button hover={hoverScale} press={pressScale}>
|
|
312
|
-
Interactive Button
|
|
313
|
-
</animate.button>
|
|
314
|
-
|
|
315
|
-
// Exit animations
|
|
316
|
-
<animate.div exit={exitFade}>Fades out</animate.div>
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
**Available Recipes:**
|
|
320
|
-
|
|
321
|
-
- **Fade**: `fadeIn`, `fadeOut`, `fadeInUp`, `fadeInDown`, `fadeInLeft`, `fadeInRight`
|
|
322
|
-
- **Slide**: `slideInUp`, `slideInDown`, `slideInLeft`, `slideInRight`, `slideOutUp`, `slideOutDown`, `slideOutLeft`, `slideOutRight`
|
|
323
|
-
- **Scale**: `scaleIn`, `scaleOut`, `scaleUp`, `scaleDown`
|
|
324
|
-
- **Bounce**: `bounceIn`, `bounceOut`
|
|
325
|
-
- **Rotate**: `rotateIn`, `rotateOut`, `spin`
|
|
326
|
-
- **Zoom**: `zoomIn`, `zoomOut`
|
|
327
|
-
- **Flip**: `flipX`, `flipY`
|
|
328
|
-
- **Combined**: `slideFadeIn`, `slideFadeOut`, `scaleFadeIn`, `scaleFadeOut`
|
|
329
|
-
- **Hover**: `hoverScale`, `hoverLift`, `hoverGlow`
|
|
330
|
-
- **Press**: `pressScale`, `pressDown`
|
|
331
|
-
- **Exit**: `exitFade`, `exitSlideUp`, `exitSlideDown`, `exitScale`
|
|
332
|
-
|
|
333
|
-
### 5. Presence Component
|
|
334
|
-
|
|
335
|
-
`Presence` manages mount and unmount animations for components:
|
|
336
|
-
|
|
337
|
-
```tsx
|
|
338
|
-
import { Presence, animate, withSpring } from 'react-ui-animate';
|
|
339
|
-
|
|
340
|
-
function List() {
|
|
341
|
-
const [items, setItems] = useState(['Item 1', 'Item 2']);
|
|
342
|
-
|
|
343
|
-
return (
|
|
344
|
-
<Presence mode="sync" onExitComplete={() => console.log('All exited')}>
|
|
345
|
-
{items.map((item) => (
|
|
346
|
-
<animate.div
|
|
347
|
-
key={item}
|
|
348
|
-
animate={{ opacity: withSpring(1) }}
|
|
349
|
-
exit={{ opacity: withSpring(0) }}
|
|
350
|
-
>
|
|
351
|
-
{item}
|
|
352
|
-
</animate.div>
|
|
353
|
-
))}
|
|
354
|
-
</Presence>
|
|
355
|
-
);
|
|
356
|
-
}
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
**Presence Props:**
|
|
360
|
-
|
|
361
|
-
- `mode`: `'sync'` (default) | `'wait'` | `'popLayout'`
|
|
362
|
-
- `sync`: Exiting and entering animate simultaneously
|
|
363
|
-
- `wait`: Exiting complete before entering start
|
|
364
|
-
- `popLayout`: Exiting removed from layout immediately
|
|
365
|
-
- `initial`: Skip enter animation on initial mount (default: `true`)
|
|
366
|
-
- `onExitComplete`: Callback when all exits complete
|
|
367
|
-
|
|
368
|
-
**Presence Hooks:**
|
|
369
|
-
|
|
370
|
-
```tsx
|
|
371
|
-
import { usePresence, useIsPresent } from 'react-ui-animate';
|
|
372
|
-
|
|
373
|
-
function AnimatedItem() {
|
|
374
|
-
const [isPresent, onExitComplete] = usePresence();
|
|
375
|
-
const isPresent2 = useIsPresent(); // Simple boolean check
|
|
376
|
-
|
|
377
|
-
// Use isPresent to conditionally render or animate
|
|
378
|
-
return isPresent ? <div>Content</div> : null;
|
|
379
|
-
}
|
|
380
|
-
```
|
|
381
|
-
|
|
382
|
-
### 6. Gestures
|
|
383
|
-
|
|
384
|
-
React to user gestures with built-in hooks:
|
|
385
|
-
|
|
386
|
-
#### useDrag
|
|
387
|
-
|
|
388
|
-
Handle drag gestures:
|
|
389
|
-
|
|
390
|
-
```tsx
|
|
391
|
-
import { useValue, animate, useDrag, withSpring } from 'react-ui-animate';
|
|
392
|
-
|
|
393
|
-
function Draggable() {
|
|
394
|
-
const ref = useRef(null);
|
|
395
|
-
const [x, setX] = useValue(0);
|
|
396
|
-
const [y, setY] = useValue(0);
|
|
397
|
-
|
|
398
|
-
useDrag(ref, ({ down, movement }) => {
|
|
399
|
-
if (down) {
|
|
400
|
-
setX(movement.x);
|
|
401
|
-
setY(movement.y);
|
|
402
|
-
} else {
|
|
403
|
-
setX(withSpring(0));
|
|
404
|
-
setY(withSpring(0));
|
|
405
|
-
}
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
return (
|
|
409
|
-
<animate.div
|
|
410
|
-
ref={ref}
|
|
411
|
-
style={{
|
|
412
|
-
width: 100,
|
|
413
|
-
height: 100,
|
|
414
|
-
backgroundColor: 'blue',
|
|
415
|
-
translateX: x,
|
|
416
|
-
translateY: y,
|
|
417
|
-
}}
|
|
418
|
-
/>
|
|
419
|
-
);
|
|
420
|
-
}
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
#### useMove
|
|
424
|
-
|
|
425
|
-
Track pointer movement:
|
|
426
|
-
|
|
427
|
-
```tsx
|
|
428
|
-
import { useMove } from 'react-ui-animate';
|
|
429
|
-
|
|
430
|
-
useMove(ref, ({ movement }) => {
|
|
431
|
-
console.log('Moving:', movement.x, movement.y);
|
|
432
|
-
});
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
#### useScroll
|
|
436
|
-
|
|
437
|
-
React to scroll events:
|
|
438
|
-
|
|
439
|
-
```tsx
|
|
440
|
-
import { useScroll } from 'react-ui-animate';
|
|
441
|
-
|
|
442
|
-
useScroll(ref, ({ scroll }) => {
|
|
443
|
-
console.log('Scrolled:', scroll.x, scroll.y);
|
|
444
|
-
});
|
|
445
|
-
```
|
|
446
|
-
|
|
447
|
-
#### useWheel
|
|
448
|
-
|
|
449
|
-
Handle wheel events:
|
|
450
|
-
|
|
451
|
-
```tsx
|
|
452
|
-
import { useWheel } from 'react-ui-animate';
|
|
453
|
-
|
|
454
|
-
useWheel(ref, ({ delta }) => {
|
|
455
|
-
console.log('Wheel delta:', delta.x, delta.y);
|
|
456
|
-
});
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
#### useScrollProgress
|
|
460
|
-
|
|
461
|
-
Track scroll progress:
|
|
462
|
-
|
|
463
|
-
```tsx
|
|
464
|
-
import { useScrollProgress } from 'react-ui-animate';
|
|
465
|
-
|
|
466
|
-
const progress = useScrollProgress(ref, {
|
|
467
|
-
start: 0, // Start tracking at 0% scroll
|
|
468
|
-
end: 100, // End tracking at 100% scroll
|
|
469
|
-
axis: 'y', // 'x' or 'y'
|
|
470
|
-
});
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
### 7. Utilities
|
|
474
|
-
|
|
475
|
-
#### makeAnimated
|
|
476
|
-
|
|
477
|
-
Create custom animated components:
|
|
478
|
-
|
|
479
|
-
```tsx
|
|
480
|
-
import { makeAnimated } from 'react-ui-animate';
|
|
481
|
-
|
|
482
|
-
const AnimatedButton = makeAnimated('button');
|
|
483
|
-
const AnimatedSection = makeAnimated('section');
|
|
484
|
-
|
|
485
|
-
<AnimatedButton animate={{ scale: withSpring(1.1) }}>
|
|
486
|
-
Custom Button
|
|
487
|
-
</AnimatedButton>
|
|
488
|
-
```
|
|
489
|
-
|
|
490
|
-
#### to (Interpolation)
|
|
491
|
-
|
|
492
|
-
Map values between ranges:
|
|
493
|
-
|
|
494
|
-
```tsx
|
|
495
|
-
import { to } from 'react-ui-animate';
|
|
496
|
-
|
|
497
|
-
const interpolate = to([0, 100], [0, 500]);
|
|
498
|
-
interpolate(50); // Returns 250
|
|
499
|
-
|
|
500
|
-
// Color interpolation
|
|
501
|
-
const colorInterpolate = to([0, 1], ['red', 'blue']);
|
|
502
|
-
colorInterpolate(0.5); // Returns interpolated color
|
|
503
|
-
```
|
|
504
|
-
|
|
505
|
-
#### combine
|
|
506
|
-
|
|
507
|
-
Combine multiple animated values:
|
|
508
|
-
|
|
509
|
-
```tsx
|
|
510
|
-
import { combine, useValue } from 'react-ui-animate';
|
|
511
|
-
|
|
512
|
-
const [x, setX] = useValue(0);
|
|
513
|
-
const [y, setY] = useValue(0);
|
|
514
|
-
const combined = combine(x, y, (x, y) => x + y);
|
|
515
|
-
```
|
|
516
|
-
|
|
517
|
-
### 8. Additional Hooks
|
|
518
|
-
|
|
519
|
-
#### useInView
|
|
520
|
-
|
|
521
|
-
Detect when elements enter the viewport:
|
|
522
|
-
|
|
523
|
-
```tsx
|
|
524
|
-
import { useInView } from 'react-ui-animate';
|
|
525
|
-
|
|
526
|
-
const ref = useRef(null);
|
|
527
|
-
const isInView = useInView(ref, {
|
|
528
|
-
threshold: 0.5,
|
|
529
|
-
once: true,
|
|
530
|
-
});
|
|
531
|
-
```
|
|
532
|
-
|
|
533
|
-
#### useOutsideClick
|
|
534
|
-
|
|
535
|
-
Detect clicks outside an element:
|
|
536
|
-
|
|
537
|
-
```tsx
|
|
538
|
-
import { useOutsideClick } from 'react-ui-animate';
|
|
539
|
-
|
|
540
|
-
const ref = useRef(null);
|
|
541
|
-
useOutsideClick(ref, () => {
|
|
542
|
-
console.log('Clicked outside!');
|
|
543
|
-
});
|
|
544
|
-
```
|
|
545
|
-
|
|
546
|
-
---
|
|
547
|
-
|
|
548
|
-
## Complete Examples
|
|
549
|
-
|
|
550
|
-
### Modal with Exit Animation
|
|
551
|
-
|
|
552
|
-
```tsx
|
|
553
|
-
import { useState, useRef } from 'react';
|
|
554
|
-
import {
|
|
555
|
-
Presence,
|
|
556
|
-
animate,
|
|
557
|
-
useOutsideClick,
|
|
558
|
-
withSpring,
|
|
559
|
-
withTiming,
|
|
560
|
-
} from 'react-ui-animate';
|
|
561
|
-
|
|
562
|
-
function Modal({ isOpen, onClose }) {
|
|
563
|
-
const ref = useRef(null);
|
|
564
|
-
useOutsideClick(ref, onClose);
|
|
565
|
-
|
|
566
|
-
return (
|
|
567
|
-
<Presence>
|
|
568
|
-
{isOpen && (
|
|
569
|
-
<>
|
|
570
|
-
{/* Backdrop */}
|
|
571
|
-
<animate.div
|
|
572
|
-
key="backdrop"
|
|
573
|
-
style={{
|
|
574
|
-
position: 'fixed',
|
|
575
|
-
inset: 0,
|
|
576
|
-
backgroundColor: 'rgba(0,0,0,0)',
|
|
577
|
-
opacity: 0,
|
|
578
|
-
}}
|
|
579
|
-
animate={{
|
|
580
|
-
backgroundColor: withTiming('rgba(0,0,0,0.5)'),
|
|
581
|
-
opacity: withTiming(1),
|
|
582
|
-
}}
|
|
583
|
-
exit={{
|
|
584
|
-
backgroundColor: withTiming('rgba(0,0,0,0)'),
|
|
585
|
-
opacity: withTiming(0),
|
|
586
|
-
}}
|
|
587
|
-
/>
|
|
588
|
-
|
|
589
|
-
{/* Modal */}
|
|
590
|
-
<animate.div
|
|
591
|
-
key="modal"
|
|
592
|
-
ref={ref}
|
|
593
|
-
style={{
|
|
594
|
-
position: 'fixed',
|
|
595
|
-
inset: 0,
|
|
596
|
-
display: 'flex',
|
|
597
|
-
alignItems: 'center',
|
|
598
|
-
justifyContent: 'center',
|
|
599
|
-
scale: 0.8,
|
|
600
|
-
opacity: 0,
|
|
601
|
-
}}
|
|
602
|
-
animate={{
|
|
603
|
-
scale: withSpring(1),
|
|
604
|
-
opacity: withSpring(1),
|
|
605
|
-
}}
|
|
606
|
-
exit={{
|
|
607
|
-
scale: withSpring(0.8),
|
|
608
|
-
opacity: withSpring(0),
|
|
609
|
-
}}
|
|
610
|
-
>
|
|
611
|
-
<div style={{ backgroundColor: 'white', padding: 20 }}>
|
|
612
|
-
Modal Content
|
|
613
|
-
</div>
|
|
614
|
-
</animate.div>
|
|
615
|
-
</>
|
|
616
|
-
)}
|
|
617
|
-
</Presence>
|
|
618
|
-
);
|
|
619
|
-
}
|
|
620
|
-
```
|
|
621
|
-
|
|
622
|
-
### Scroll-Triggered Animations
|
|
623
|
-
|
|
624
|
-
```tsx
|
|
625
|
-
import { animate, withSpring, withTiming } from 'react-ui-animate';
|
|
626
|
-
|
|
627
|
-
function FeatureCard({ title, description }) {
|
|
628
|
-
return (
|
|
629
|
-
<animate.div
|
|
630
|
-
style={{
|
|
631
|
-
opacity: 0,
|
|
632
|
-
translateY: 50,
|
|
633
|
-
scale: 0.9,
|
|
634
|
-
}}
|
|
635
|
-
view={{
|
|
636
|
-
opacity: withTiming(1, { duration: 600 }),
|
|
637
|
-
translateY: withSpring(0),
|
|
638
|
-
scale: withSpring(1),
|
|
639
|
-
}}
|
|
640
|
-
viewOptions={{ threshold: 0.3, once: true }}
|
|
641
|
-
>
|
|
642
|
-
<h3>{title}</h3>
|
|
643
|
-
<p>{description}</p>
|
|
644
|
-
</animate.div>
|
|
645
|
-
);
|
|
646
|
-
}
|
|
647
|
-
```
|
|
648
|
-
|
|
649
|
-
### Interactive Button
|
|
650
|
-
|
|
651
|
-
```tsx
|
|
652
|
-
import { animate, withSpring, hoverScale, pressScale } from 'react-ui-animate';
|
|
653
|
-
|
|
654
|
-
<animate.button
|
|
655
|
-
style={{
|
|
656
|
-
padding: '12px 24px',
|
|
657
|
-
backgroundColor: '#3399ff',
|
|
658
|
-
color: 'white',
|
|
659
|
-
border: 'none',
|
|
660
|
-
borderRadius: 8,
|
|
661
|
-
cursor: 'pointer',
|
|
662
|
-
}}
|
|
663
|
-
hover={hoverScale}
|
|
664
|
-
press={pressScale}
|
|
665
|
-
>
|
|
666
|
-
Click Me
|
|
667
|
-
</animate.button>
|
|
668
|
-
```
|
|
669
|
-
|
|
670
|
-
---
|
|
671
|
-
|
|
672
|
-
## API Reference
|
|
673
|
-
|
|
674
|
-
### Animation Descriptors
|
|
675
|
-
|
|
676
|
-
| Descriptor | Description | Options |
|
|
677
|
-
|------------|-------------|---------|
|
|
678
|
-
| `withSpring(to, options?)` | Physics-based spring | `stiffness`, `damping`, `mass`, `from`, callbacks |
|
|
679
|
-
| `withTiming(to, options?)` | Time-based animation | `duration`, `easing`, `from`, callbacks |
|
|
680
|
-
| `withDecay(options)` | Momentum decay | `velocity`, `clamp`, callbacks |
|
|
681
|
-
| `withSequence(animations)` | Run sequentially | `animations` array, callbacks |
|
|
682
|
-
| `withLoop(animation, iterations)` | Repeat animation | `iterations` (0 = infinite), callbacks |
|
|
683
|
-
| `withDelay(ms)` | Add delay | `delay` in milliseconds |
|
|
684
|
-
|
|
685
|
-
### Animate Component Props
|
|
686
|
-
|
|
687
|
-
| Prop | Type | Description |
|
|
688
|
-
|------|------|-------------|
|
|
689
|
-
| `animate` | `AnimateProp` | Animations on mount/update |
|
|
690
|
-
| `exit` | `AnimateProp` | Animations on unmount (requires `Presence`) |
|
|
691
|
-
| `hover` | `AnimateProp` | Animations on hover |
|
|
692
|
-
| `press` | `AnimateProp` | Animations on press (mousedown/touchstart) |
|
|
693
|
-
| `focus` | `AnimateProp` | Animations on focus |
|
|
694
|
-
| `view` | `AnimateProp` | Animations when entering viewport |
|
|
695
|
-
| `viewOptions` | `UseInViewOptions` | IntersectionObserver options |
|
|
696
|
-
|
|
697
|
-
### Callbacks
|
|
698
|
-
|
|
699
|
-
All descriptors support optional callbacks:
|
|
700
|
-
|
|
701
|
-
```tsx
|
|
702
|
-
withSpring(100, {
|
|
703
|
-
onStart: () => console.log('Started'),
|
|
704
|
-
onChange: (value) => console.log('Value:', value),
|
|
705
|
-
onComplete: () => console.log('Completed'),
|
|
706
|
-
})
|
|
707
|
-
```
|
|
708
|
-
|
|
709
|
-
---
|
|
710
|
-
|
|
711
|
-
## TypeScript Support
|
|
712
|
-
|
|
713
|
-
Full TypeScript support is included. All components, hooks, and utilities are fully typed.
|
|
714
|
-
|
|
715
|
-
---
|
|
716
|
-
|
|
717
|
-
## Performance
|
|
718
|
-
|
|
719
|
-
- Animations run on the GPU when possible
|
|
720
|
-
- Automatic batching of style updates
|
|
721
|
-
- Optimized re-renders
|
|
722
|
-
- Tree-shakeable exports
|
|
723
|
-
|
|
724
|
-
---
|
|
725
|
-
|
|
726
|
-
## Browser Support
|
|
727
|
-
|
|
728
|
-
- Chrome/Edge (latest)
|
|
729
|
-
- Firefox (latest)
|
|
730
|
-
- Safari (latest)
|
|
731
|
-
- Mobile browsers (iOS Safari, Chrome Mobile)
|
|
732
|
-
|
|
733
|
-
---
|
|
734
|
-
|
|
735
|
-
## Documentation
|
|
736
|
-
|
|
737
|
-
For detailed documentation and more examples, visit the [official documentation](https://react-ui-animate.js.org/).
|
|
738
|
-
|
|
739
|
-
---
|
|
740
|
-
|
|
741
|
-
## License
|
|
742
|
-
|
|
743
|
-
MIT © [Dipesh Rai](https://github.com/dipeshrai123)
|
|
1
|
+
# React UI Animate
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/js/react-ui-animate)
|
|
4
|
+
|
|
5
|
+
> Create smooth animations and interactive gestures in React applications effortlessly.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install react-ui-animate
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
yarn add react-ui-animate
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
The `react-ui-animate` library provides a declarative way to add animations and gestures to your React components. Here's a simple example:
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { animate, withSpring } from 'react-ui-animate';
|
|
25
|
+
|
|
26
|
+
function App() {
|
|
27
|
+
return (
|
|
28
|
+
<animate.div
|
|
29
|
+
style={{
|
|
30
|
+
width: 100,
|
|
31
|
+
height: 100,
|
|
32
|
+
backgroundColor: 'blue',
|
|
33
|
+
scale: 0.5,
|
|
34
|
+
opacity: 0,
|
|
35
|
+
}}
|
|
36
|
+
animate={{
|
|
37
|
+
scale: withSpring(1),
|
|
38
|
+
opacity: withSpring(1),
|
|
39
|
+
}}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Core Concepts
|
|
48
|
+
|
|
49
|
+
### 1. Animate Component
|
|
50
|
+
|
|
51
|
+
The `animate` object provides animated versions of all HTML elements. Use `animate.div`, `animate.button`, `animate.span`, etc., just like regular React elements.
|
|
52
|
+
|
|
53
|
+
#### Basic Animation
|
|
54
|
+
|
|
55
|
+
Use the `animate` prop to define animations that run when the component mounts or when the prop changes:
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import { animate, withSpring, withTiming } from 'react-ui-animate';
|
|
59
|
+
|
|
60
|
+
<animate.div
|
|
61
|
+
style={{
|
|
62
|
+
width: 100,
|
|
63
|
+
height: 100,
|
|
64
|
+
backgroundColor: 'red',
|
|
65
|
+
translateX: 0,
|
|
66
|
+
}}
|
|
67
|
+
animate={{
|
|
68
|
+
translateX: withSpring(200),
|
|
69
|
+
backgroundColor: withTiming('blue', { duration: 500 }),
|
|
70
|
+
}}
|
|
71
|
+
/>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
#### Exit Animations with Presence
|
|
75
|
+
|
|
76
|
+
Wrap components in `Presence` to enable exit animations when they're removed:
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
import { Presence, animate, withSpring, withTiming } from 'react-ui-animate';
|
|
80
|
+
|
|
81
|
+
function Modal({ isOpen, onClose }) {
|
|
82
|
+
return (
|
|
83
|
+
<Presence>
|
|
84
|
+
{isOpen && (
|
|
85
|
+
<animate.div
|
|
86
|
+
key="modal"
|
|
87
|
+
style={{
|
|
88
|
+
opacity: 0,
|
|
89
|
+
scale: 0.8,
|
|
90
|
+
}}
|
|
91
|
+
animate={{
|
|
92
|
+
opacity: withTiming(1),
|
|
93
|
+
scale: withSpring(1),
|
|
94
|
+
}}
|
|
95
|
+
exit={{
|
|
96
|
+
opacity: withTiming(0),
|
|
97
|
+
scale: withSpring(0.8),
|
|
98
|
+
}}
|
|
99
|
+
>
|
|
100
|
+
Modal Content
|
|
101
|
+
</animate.div>
|
|
102
|
+
)}
|
|
103
|
+
</Presence>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### State-Based Animations
|
|
109
|
+
|
|
110
|
+
React to user interactions with `hover`, `press`, `focus`, and `view` props:
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
<animate.button
|
|
114
|
+
style={{
|
|
115
|
+
scale: 1,
|
|
116
|
+
backgroundColor: '#3399ff',
|
|
117
|
+
}}
|
|
118
|
+
hover={{
|
|
119
|
+
scale: withSpring(1.05),
|
|
120
|
+
backgroundColor: withTiming('#4da6ff'),
|
|
121
|
+
}}
|
|
122
|
+
press={{
|
|
123
|
+
scale: withSpring(0.95),
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
126
|
+
Hover Me
|
|
127
|
+
</animate.button>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**View Animations** - Animate when elements enter the viewport:
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
<animate.div
|
|
134
|
+
style={{
|
|
135
|
+
opacity: 0,
|
|
136
|
+
translateY: 50,
|
|
137
|
+
}}
|
|
138
|
+
view={{
|
|
139
|
+
opacity: withTiming(1),
|
|
140
|
+
translateY: withSpring(0),
|
|
141
|
+
}}
|
|
142
|
+
viewOptions={{ threshold: 0.3, once: true }}
|
|
143
|
+
>
|
|
144
|
+
This animates when scrolled into view
|
|
145
|
+
</animate.div>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 2. useValue Hook
|
|
149
|
+
|
|
150
|
+
Create and control animated values programmatically:
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
import { useValue, animate, withSpring, withSequence, withTiming } from 'react-ui-animate';
|
|
154
|
+
|
|
155
|
+
function AnimatedBox() {
|
|
156
|
+
const [width, setWidth] = useValue(100);
|
|
157
|
+
const [x, setX] = useValue(0);
|
|
158
|
+
|
|
159
|
+
return (
|
|
160
|
+
<>
|
|
161
|
+
<button onClick={() => setWidth(withSpring(200))}>
|
|
162
|
+
Expand
|
|
163
|
+
</button>
|
|
164
|
+
<button onClick={() => setX(withSequence([
|
|
165
|
+
withTiming(100, { duration: 300 }),
|
|
166
|
+
withTiming(200, { duration: 300 }),
|
|
167
|
+
withTiming(0, { duration: 300 }),
|
|
168
|
+
]))}>
|
|
169
|
+
Sequence
|
|
170
|
+
</button>
|
|
171
|
+
|
|
172
|
+
<animate.div
|
|
173
|
+
style={{
|
|
174
|
+
width,
|
|
175
|
+
height: 100,
|
|
176
|
+
backgroundColor: 'red',
|
|
177
|
+
translateX: x,
|
|
178
|
+
}}
|
|
179
|
+
/>
|
|
180
|
+
</>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Interpolation** - Map values to different ranges:
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
const [progress, setProgress] = useValue(0);
|
|
189
|
+
|
|
190
|
+
<animate.div
|
|
191
|
+
style={{
|
|
192
|
+
backgroundColor: progress.to([0, 1], ['red', 'blue']),
|
|
193
|
+
translateX: progress.to([0, 1], [0, 500]),
|
|
194
|
+
}}
|
|
195
|
+
/>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Controls** - Control animations programmatically:
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
const [value, setValue, controls] = useValue(0);
|
|
202
|
+
|
|
203
|
+
// Start animation
|
|
204
|
+
setValue(withSpring(100));
|
|
205
|
+
|
|
206
|
+
// Control playback
|
|
207
|
+
controls.pause();
|
|
208
|
+
controls.resume();
|
|
209
|
+
controls.cancel();
|
|
210
|
+
controls.reset();
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### 3. Animation Descriptors
|
|
214
|
+
|
|
215
|
+
Animation descriptors define how animations behave. They can be used with both `animate` components and `useValue`.
|
|
216
|
+
|
|
217
|
+
#### withSpring
|
|
218
|
+
|
|
219
|
+
Creates physics-based spring animations:
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
withSpring(100, {
|
|
223
|
+
stiffness: 100, // Spring stiffness (default: 100)
|
|
224
|
+
damping: 15, // Spring damping (default: 15)
|
|
225
|
+
mass: 1, // Spring mass (default: 1)
|
|
226
|
+
})
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
#### withTiming
|
|
230
|
+
|
|
231
|
+
Creates time-based animations with easing:
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
withTiming(100, {
|
|
235
|
+
duration: 500, // Duration in milliseconds
|
|
236
|
+
easing: Easing.easeInOut, // Easing function
|
|
237
|
+
})
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
#### withDecay
|
|
241
|
+
|
|
242
|
+
Creates momentum-based decay animations:
|
|
243
|
+
|
|
244
|
+
```tsx
|
|
245
|
+
withDecay({
|
|
246
|
+
velocity: 100, // Initial velocity
|
|
247
|
+
clamp: [0, 500], // Optional: clamp values
|
|
248
|
+
})
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
#### withSequence
|
|
252
|
+
|
|
253
|
+
Runs animations one after another:
|
|
254
|
+
|
|
255
|
+
```tsx
|
|
256
|
+
withSequence([
|
|
257
|
+
withTiming(100, { duration: 300 }),
|
|
258
|
+
withTiming(200, { duration: 300 }),
|
|
259
|
+
withSpring(0, { stiffness: 200 }),
|
|
260
|
+
])
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
#### withLoop
|
|
264
|
+
|
|
265
|
+
Repeats animations:
|
|
266
|
+
|
|
267
|
+
```tsx
|
|
268
|
+
withLoop(
|
|
269
|
+
withSequence([
|
|
270
|
+
withTiming(90, { duration: 500 }),
|
|
271
|
+
withTiming(180, { duration: 500 }),
|
|
272
|
+
withTiming(360, { duration: 500 }),
|
|
273
|
+
]),
|
|
274
|
+
3 // Number of iterations (0 = infinite)
|
|
275
|
+
)
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
#### withDelay
|
|
279
|
+
|
|
280
|
+
Adds delay to animations (typically used inside `withSequence`):
|
|
281
|
+
|
|
282
|
+
```tsx
|
|
283
|
+
withSequence([
|
|
284
|
+
withDelay(500),
|
|
285
|
+
withTiming(1, { duration: 500 }),
|
|
286
|
+
])
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### 4. Animation Recipes
|
|
290
|
+
|
|
291
|
+
Pre-built animation recipes for common use cases:
|
|
292
|
+
|
|
293
|
+
```tsx
|
|
294
|
+
import {
|
|
295
|
+
animate,
|
|
296
|
+
fadeIn,
|
|
297
|
+
slideInUp,
|
|
298
|
+
scaleIn,
|
|
299
|
+
bounceIn,
|
|
300
|
+
hoverScale,
|
|
301
|
+
pressScale,
|
|
302
|
+
} from 'react-ui-animate';
|
|
303
|
+
|
|
304
|
+
// Enter animations
|
|
305
|
+
<animate.div animate={fadeIn}>Fades in</animate.div>
|
|
306
|
+
<animate.div animate={slideInUp}>Slides up</animate.div>
|
|
307
|
+
<animate.div animate={scaleIn}>Scales in</animate.div>
|
|
308
|
+
<animate.div animate={bounceIn}>Bounces in</animate.div>
|
|
309
|
+
|
|
310
|
+
// State animations
|
|
311
|
+
<animate.button hover={hoverScale} press={pressScale}>
|
|
312
|
+
Interactive Button
|
|
313
|
+
</animate.button>
|
|
314
|
+
|
|
315
|
+
// Exit animations
|
|
316
|
+
<animate.div exit={exitFade}>Fades out</animate.div>
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**Available Recipes:**
|
|
320
|
+
|
|
321
|
+
- **Fade**: `fadeIn`, `fadeOut`, `fadeInUp`, `fadeInDown`, `fadeInLeft`, `fadeInRight`
|
|
322
|
+
- **Slide**: `slideInUp`, `slideInDown`, `slideInLeft`, `slideInRight`, `slideOutUp`, `slideOutDown`, `slideOutLeft`, `slideOutRight`
|
|
323
|
+
- **Scale**: `scaleIn`, `scaleOut`, `scaleUp`, `scaleDown`
|
|
324
|
+
- **Bounce**: `bounceIn`, `bounceOut`
|
|
325
|
+
- **Rotate**: `rotateIn`, `rotateOut`, `spin`
|
|
326
|
+
- **Zoom**: `zoomIn`, `zoomOut`
|
|
327
|
+
- **Flip**: `flipX`, `flipY`
|
|
328
|
+
- **Combined**: `slideFadeIn`, `slideFadeOut`, `scaleFadeIn`, `scaleFadeOut`
|
|
329
|
+
- **Hover**: `hoverScale`, `hoverLift`, `hoverGlow`
|
|
330
|
+
- **Press**: `pressScale`, `pressDown`
|
|
331
|
+
- **Exit**: `exitFade`, `exitSlideUp`, `exitSlideDown`, `exitScale`
|
|
332
|
+
|
|
333
|
+
### 5. Presence Component
|
|
334
|
+
|
|
335
|
+
`Presence` manages mount and unmount animations for components:
|
|
336
|
+
|
|
337
|
+
```tsx
|
|
338
|
+
import { Presence, animate, withSpring } from 'react-ui-animate';
|
|
339
|
+
|
|
340
|
+
function List() {
|
|
341
|
+
const [items, setItems] = useState(['Item 1', 'Item 2']);
|
|
342
|
+
|
|
343
|
+
return (
|
|
344
|
+
<Presence mode="sync" onExitComplete={() => console.log('All exited')}>
|
|
345
|
+
{items.map((item) => (
|
|
346
|
+
<animate.div
|
|
347
|
+
key={item}
|
|
348
|
+
animate={{ opacity: withSpring(1) }}
|
|
349
|
+
exit={{ opacity: withSpring(0) }}
|
|
350
|
+
>
|
|
351
|
+
{item}
|
|
352
|
+
</animate.div>
|
|
353
|
+
))}
|
|
354
|
+
</Presence>
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
**Presence Props:**
|
|
360
|
+
|
|
361
|
+
- `mode`: `'sync'` (default) | `'wait'` | `'popLayout'`
|
|
362
|
+
- `sync`: Exiting and entering animate simultaneously
|
|
363
|
+
- `wait`: Exiting complete before entering start
|
|
364
|
+
- `popLayout`: Exiting removed from layout immediately
|
|
365
|
+
- `initial`: Skip enter animation on initial mount (default: `true`)
|
|
366
|
+
- `onExitComplete`: Callback when all exits complete
|
|
367
|
+
|
|
368
|
+
**Presence Hooks:**
|
|
369
|
+
|
|
370
|
+
```tsx
|
|
371
|
+
import { usePresence, useIsPresent } from 'react-ui-animate';
|
|
372
|
+
|
|
373
|
+
function AnimatedItem() {
|
|
374
|
+
const [isPresent, onExitComplete] = usePresence();
|
|
375
|
+
const isPresent2 = useIsPresent(); // Simple boolean check
|
|
376
|
+
|
|
377
|
+
// Use isPresent to conditionally render or animate
|
|
378
|
+
return isPresent ? <div>Content</div> : null;
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### 6. Gestures
|
|
383
|
+
|
|
384
|
+
React to user gestures with built-in hooks:
|
|
385
|
+
|
|
386
|
+
#### useDrag
|
|
387
|
+
|
|
388
|
+
Handle drag gestures:
|
|
389
|
+
|
|
390
|
+
```tsx
|
|
391
|
+
import { useValue, animate, useDrag, withSpring } from 'react-ui-animate';
|
|
392
|
+
|
|
393
|
+
function Draggable() {
|
|
394
|
+
const ref = useRef(null);
|
|
395
|
+
const [x, setX] = useValue(0);
|
|
396
|
+
const [y, setY] = useValue(0);
|
|
397
|
+
|
|
398
|
+
useDrag(ref, ({ down, movement }) => {
|
|
399
|
+
if (down) {
|
|
400
|
+
setX(movement.x);
|
|
401
|
+
setY(movement.y);
|
|
402
|
+
} else {
|
|
403
|
+
setX(withSpring(0));
|
|
404
|
+
setY(withSpring(0));
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
return (
|
|
409
|
+
<animate.div
|
|
410
|
+
ref={ref}
|
|
411
|
+
style={{
|
|
412
|
+
width: 100,
|
|
413
|
+
height: 100,
|
|
414
|
+
backgroundColor: 'blue',
|
|
415
|
+
translateX: x,
|
|
416
|
+
translateY: y,
|
|
417
|
+
}}
|
|
418
|
+
/>
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
#### useMove
|
|
424
|
+
|
|
425
|
+
Track pointer movement:
|
|
426
|
+
|
|
427
|
+
```tsx
|
|
428
|
+
import { useMove } from 'react-ui-animate';
|
|
429
|
+
|
|
430
|
+
useMove(ref, ({ movement }) => {
|
|
431
|
+
console.log('Moving:', movement.x, movement.y);
|
|
432
|
+
});
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
#### useScroll
|
|
436
|
+
|
|
437
|
+
React to scroll events:
|
|
438
|
+
|
|
439
|
+
```tsx
|
|
440
|
+
import { useScroll } from 'react-ui-animate';
|
|
441
|
+
|
|
442
|
+
useScroll(ref, ({ scroll }) => {
|
|
443
|
+
console.log('Scrolled:', scroll.x, scroll.y);
|
|
444
|
+
});
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
#### useWheel
|
|
448
|
+
|
|
449
|
+
Handle wheel events:
|
|
450
|
+
|
|
451
|
+
```tsx
|
|
452
|
+
import { useWheel } from 'react-ui-animate';
|
|
453
|
+
|
|
454
|
+
useWheel(ref, ({ delta }) => {
|
|
455
|
+
console.log('Wheel delta:', delta.x, delta.y);
|
|
456
|
+
});
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
#### useScrollProgress
|
|
460
|
+
|
|
461
|
+
Track scroll progress:
|
|
462
|
+
|
|
463
|
+
```tsx
|
|
464
|
+
import { useScrollProgress } from 'react-ui-animate';
|
|
465
|
+
|
|
466
|
+
const progress = useScrollProgress(ref, {
|
|
467
|
+
start: 0, // Start tracking at 0% scroll
|
|
468
|
+
end: 100, // End tracking at 100% scroll
|
|
469
|
+
axis: 'y', // 'x' or 'y'
|
|
470
|
+
});
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### 7. Utilities
|
|
474
|
+
|
|
475
|
+
#### makeAnimated
|
|
476
|
+
|
|
477
|
+
Create custom animated components:
|
|
478
|
+
|
|
479
|
+
```tsx
|
|
480
|
+
import { makeAnimated } from 'react-ui-animate';
|
|
481
|
+
|
|
482
|
+
const AnimatedButton = makeAnimated('button');
|
|
483
|
+
const AnimatedSection = makeAnimated('section');
|
|
484
|
+
|
|
485
|
+
<AnimatedButton animate={{ scale: withSpring(1.1) }}>
|
|
486
|
+
Custom Button
|
|
487
|
+
</AnimatedButton>
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
#### to (Interpolation)
|
|
491
|
+
|
|
492
|
+
Map values between ranges:
|
|
493
|
+
|
|
494
|
+
```tsx
|
|
495
|
+
import { to } from 'react-ui-animate';
|
|
496
|
+
|
|
497
|
+
const interpolate = to([0, 100], [0, 500]);
|
|
498
|
+
interpolate(50); // Returns 250
|
|
499
|
+
|
|
500
|
+
// Color interpolation
|
|
501
|
+
const colorInterpolate = to([0, 1], ['red', 'blue']);
|
|
502
|
+
colorInterpolate(0.5); // Returns interpolated color
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
#### combine
|
|
506
|
+
|
|
507
|
+
Combine multiple animated values:
|
|
508
|
+
|
|
509
|
+
```tsx
|
|
510
|
+
import { combine, useValue } from 'react-ui-animate';
|
|
511
|
+
|
|
512
|
+
const [x, setX] = useValue(0);
|
|
513
|
+
const [y, setY] = useValue(0);
|
|
514
|
+
const combined = combine(x, y, (x, y) => x + y);
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### 8. Additional Hooks
|
|
518
|
+
|
|
519
|
+
#### useInView
|
|
520
|
+
|
|
521
|
+
Detect when elements enter the viewport:
|
|
522
|
+
|
|
523
|
+
```tsx
|
|
524
|
+
import { useInView } from 'react-ui-animate';
|
|
525
|
+
|
|
526
|
+
const ref = useRef(null);
|
|
527
|
+
const isInView = useInView(ref, {
|
|
528
|
+
threshold: 0.5,
|
|
529
|
+
once: true,
|
|
530
|
+
});
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
#### useOutsideClick
|
|
534
|
+
|
|
535
|
+
Detect clicks outside an element:
|
|
536
|
+
|
|
537
|
+
```tsx
|
|
538
|
+
import { useOutsideClick } from 'react-ui-animate';
|
|
539
|
+
|
|
540
|
+
const ref = useRef(null);
|
|
541
|
+
useOutsideClick(ref, () => {
|
|
542
|
+
console.log('Clicked outside!');
|
|
543
|
+
});
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
---
|
|
547
|
+
|
|
548
|
+
## Complete Examples
|
|
549
|
+
|
|
550
|
+
### Modal with Exit Animation
|
|
551
|
+
|
|
552
|
+
```tsx
|
|
553
|
+
import { useState, useRef } from 'react';
|
|
554
|
+
import {
|
|
555
|
+
Presence,
|
|
556
|
+
animate,
|
|
557
|
+
useOutsideClick,
|
|
558
|
+
withSpring,
|
|
559
|
+
withTiming,
|
|
560
|
+
} from 'react-ui-animate';
|
|
561
|
+
|
|
562
|
+
function Modal({ isOpen, onClose }) {
|
|
563
|
+
const ref = useRef(null);
|
|
564
|
+
useOutsideClick(ref, onClose);
|
|
565
|
+
|
|
566
|
+
return (
|
|
567
|
+
<Presence>
|
|
568
|
+
{isOpen && (
|
|
569
|
+
<>
|
|
570
|
+
{/* Backdrop */}
|
|
571
|
+
<animate.div
|
|
572
|
+
key="backdrop"
|
|
573
|
+
style={{
|
|
574
|
+
position: 'fixed',
|
|
575
|
+
inset: 0,
|
|
576
|
+
backgroundColor: 'rgba(0,0,0,0)',
|
|
577
|
+
opacity: 0,
|
|
578
|
+
}}
|
|
579
|
+
animate={{
|
|
580
|
+
backgroundColor: withTiming('rgba(0,0,0,0.5)'),
|
|
581
|
+
opacity: withTiming(1),
|
|
582
|
+
}}
|
|
583
|
+
exit={{
|
|
584
|
+
backgroundColor: withTiming('rgba(0,0,0,0)'),
|
|
585
|
+
opacity: withTiming(0),
|
|
586
|
+
}}
|
|
587
|
+
/>
|
|
588
|
+
|
|
589
|
+
{/* Modal */}
|
|
590
|
+
<animate.div
|
|
591
|
+
key="modal"
|
|
592
|
+
ref={ref}
|
|
593
|
+
style={{
|
|
594
|
+
position: 'fixed',
|
|
595
|
+
inset: 0,
|
|
596
|
+
display: 'flex',
|
|
597
|
+
alignItems: 'center',
|
|
598
|
+
justifyContent: 'center',
|
|
599
|
+
scale: 0.8,
|
|
600
|
+
opacity: 0,
|
|
601
|
+
}}
|
|
602
|
+
animate={{
|
|
603
|
+
scale: withSpring(1),
|
|
604
|
+
opacity: withSpring(1),
|
|
605
|
+
}}
|
|
606
|
+
exit={{
|
|
607
|
+
scale: withSpring(0.8),
|
|
608
|
+
opacity: withSpring(0),
|
|
609
|
+
}}
|
|
610
|
+
>
|
|
611
|
+
<div style={{ backgroundColor: 'white', padding: 20 }}>
|
|
612
|
+
Modal Content
|
|
613
|
+
</div>
|
|
614
|
+
</animate.div>
|
|
615
|
+
</>
|
|
616
|
+
)}
|
|
617
|
+
</Presence>
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
### Scroll-Triggered Animations
|
|
623
|
+
|
|
624
|
+
```tsx
|
|
625
|
+
import { animate, withSpring, withTiming } from 'react-ui-animate';
|
|
626
|
+
|
|
627
|
+
function FeatureCard({ title, description }) {
|
|
628
|
+
return (
|
|
629
|
+
<animate.div
|
|
630
|
+
style={{
|
|
631
|
+
opacity: 0,
|
|
632
|
+
translateY: 50,
|
|
633
|
+
scale: 0.9,
|
|
634
|
+
}}
|
|
635
|
+
view={{
|
|
636
|
+
opacity: withTiming(1, { duration: 600 }),
|
|
637
|
+
translateY: withSpring(0),
|
|
638
|
+
scale: withSpring(1),
|
|
639
|
+
}}
|
|
640
|
+
viewOptions={{ threshold: 0.3, once: true }}
|
|
641
|
+
>
|
|
642
|
+
<h3>{title}</h3>
|
|
643
|
+
<p>{description}</p>
|
|
644
|
+
</animate.div>
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
### Interactive Button
|
|
650
|
+
|
|
651
|
+
```tsx
|
|
652
|
+
import { animate, withSpring, hoverScale, pressScale } from 'react-ui-animate';
|
|
653
|
+
|
|
654
|
+
<animate.button
|
|
655
|
+
style={{
|
|
656
|
+
padding: '12px 24px',
|
|
657
|
+
backgroundColor: '#3399ff',
|
|
658
|
+
color: 'white',
|
|
659
|
+
border: 'none',
|
|
660
|
+
borderRadius: 8,
|
|
661
|
+
cursor: 'pointer',
|
|
662
|
+
}}
|
|
663
|
+
hover={hoverScale}
|
|
664
|
+
press={pressScale}
|
|
665
|
+
>
|
|
666
|
+
Click Me
|
|
667
|
+
</animate.button>
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
---
|
|
671
|
+
|
|
672
|
+
## API Reference
|
|
673
|
+
|
|
674
|
+
### Animation Descriptors
|
|
675
|
+
|
|
676
|
+
| Descriptor | Description | Options |
|
|
677
|
+
|------------|-------------|---------|
|
|
678
|
+
| `withSpring(to, options?)` | Physics-based spring | `stiffness`, `damping`, `mass`, `from`, callbacks |
|
|
679
|
+
| `withTiming(to, options?)` | Time-based animation | `duration`, `easing`, `from`, callbacks |
|
|
680
|
+
| `withDecay(options)` | Momentum decay | `velocity`, `clamp`, callbacks |
|
|
681
|
+
| `withSequence(animations)` | Run sequentially | `animations` array, callbacks |
|
|
682
|
+
| `withLoop(animation, iterations)` | Repeat animation | `iterations` (0 = infinite), callbacks |
|
|
683
|
+
| `withDelay(ms)` | Add delay | `delay` in milliseconds |
|
|
684
|
+
|
|
685
|
+
### Animate Component Props
|
|
686
|
+
|
|
687
|
+
| Prop | Type | Description |
|
|
688
|
+
|------|------|-------------|
|
|
689
|
+
| `animate` | `AnimateProp` | Animations on mount/update |
|
|
690
|
+
| `exit` | `AnimateProp` | Animations on unmount (requires `Presence`) |
|
|
691
|
+
| `hover` | `AnimateProp` | Animations on hover |
|
|
692
|
+
| `press` | `AnimateProp` | Animations on press (mousedown/touchstart) |
|
|
693
|
+
| `focus` | `AnimateProp` | Animations on focus |
|
|
694
|
+
| `view` | `AnimateProp` | Animations when entering viewport |
|
|
695
|
+
| `viewOptions` | `UseInViewOptions` | IntersectionObserver options |
|
|
696
|
+
|
|
697
|
+
### Callbacks
|
|
698
|
+
|
|
699
|
+
All descriptors support optional callbacks:
|
|
700
|
+
|
|
701
|
+
```tsx
|
|
702
|
+
withSpring(100, {
|
|
703
|
+
onStart: () => console.log('Started'),
|
|
704
|
+
onChange: (value) => console.log('Value:', value),
|
|
705
|
+
onComplete: () => console.log('Completed'),
|
|
706
|
+
})
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
---
|
|
710
|
+
|
|
711
|
+
## TypeScript Support
|
|
712
|
+
|
|
713
|
+
Full TypeScript support is included. All components, hooks, and utilities are fully typed.
|
|
714
|
+
|
|
715
|
+
---
|
|
716
|
+
|
|
717
|
+
## Performance
|
|
718
|
+
|
|
719
|
+
- Animations run on the GPU when possible
|
|
720
|
+
- Automatic batching of style updates
|
|
721
|
+
- Optimized re-renders
|
|
722
|
+
- Tree-shakeable exports
|
|
723
|
+
|
|
724
|
+
---
|
|
725
|
+
|
|
726
|
+
## Browser Support
|
|
727
|
+
|
|
728
|
+
- Chrome/Edge (latest)
|
|
729
|
+
- Firefox (latest)
|
|
730
|
+
- Safari (latest)
|
|
731
|
+
- Mobile browsers (iOS Safari, Chrome Mobile)
|
|
732
|
+
|
|
733
|
+
---
|
|
734
|
+
|
|
735
|
+
## Documentation
|
|
736
|
+
|
|
737
|
+
For detailed documentation and more examples, visit the [official documentation](https://react-ui-animate.js.org/).
|
|
738
|
+
|
|
739
|
+
---
|
|
740
|
+
|
|
741
|
+
## License
|
|
742
|
+
|
|
743
|
+
MIT © [Dipesh Rai](https://github.com/dipeshrai123)
|