react-native-ease 0.2.0 → 0.4.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/.claude-plugin/marketplace.json +21 -0
- package/.claude-plugin/plugin.json +5 -0
- package/README.md +218 -74
- package/android/src/main/java/com/ease/EaseView.kt +275 -78
- package/android/src/main/java/com/ease/EaseViewManager.kt +5 -44
- package/ios/EaseView.mm +277 -76
- package/lib/module/EaseView.js +85 -26
- package/lib/module/EaseView.js.map +1 -1
- package/lib/module/EaseView.web.js +351 -0
- package/lib/module/EaseView.web.js.map +1 -0
- package/lib/module/EaseViewNativeComponent.ts +24 -15
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/EaseView.d.ts +2 -0
- package/lib/typescript/src/EaseView.d.ts.map +1 -1
- package/lib/typescript/src/EaseView.web.d.ts +16 -0
- package/lib/typescript/src/EaseView.web.d.ts.map +1 -0
- package/lib/typescript/src/EaseViewNativeComponent.d.ts +20 -7
- package/lib/typescript/src/EaseViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +21 -2
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +7 -5
- package/skills/react-native-ease-refactor/SKILL.md +405 -0
- package/src/EaseView.tsx +116 -48
- package/src/EaseView.web.tsx +462 -0
- package/src/EaseViewNativeComponent.ts +24 -15
- package/src/index.tsx +2 -0
- package/src/types.ts +26 -2
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-native-ease-plugins",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "AppAndFlow",
|
|
5
|
+
"email": "devops@appandflow.com"
|
|
6
|
+
},
|
|
7
|
+
"metadata": {
|
|
8
|
+
"description": "Claude Code skills for react-native-ease — migrate Reanimated/Animated code to react-native-ease"
|
|
9
|
+
},
|
|
10
|
+
"plugins": [
|
|
11
|
+
{
|
|
12
|
+
"name": "react-native-ease",
|
|
13
|
+
"source": "./",
|
|
14
|
+
"description": "Scan for Animated/Reanimated code and migrate to react-native-ease",
|
|
15
|
+
"version": "0.2.0",
|
|
16
|
+
"author": {
|
|
17
|
+
"name": "AppAndFlow"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
package/README.md
CHANGED
|
@@ -1,32 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
<img width="100%" height="auto" alt="react-native-ease by App & Flow" src="https://github.com/user-attachments/assets/8006ed51-d373-4c97-9e80-9937eb9a569e" />
|
|
2
2
|
|
|
3
3
|
Lightweight declarative animations powered by platform APIs. Uses Core Animation on iOS and Animator on Android — zero JS overhead.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## About
|
|
6
|
+
App & Flow is a Montreal-based React Native engineering and consulting studio. We partner with the world’s top companies and are recommended by [Expo](https://expo.dev/consultants). Need a hand? Let’s build together. team@appandflow.com
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
- **Simple** — CSS-transition-like API. Set target values, get smooth animations. One component, a few props.
|
|
9
|
-
- **Lightweight** — Minimal native code, no C++ runtime, no custom animation engine. Just a thin declarative wrapper around what the OS already provides.
|
|
10
|
-
- **Interruptible** — Changing values mid-animation smoothly redirects to the new target. No jumps.
|
|
11
|
-
|
|
12
|
-
## Non-Goals
|
|
13
|
-
|
|
14
|
-
- **Complex gesture-driven animations** — If you need pan/pinch-driven animations, animation worklets, or shared values across components, use [react-native-reanimated](https://github.com/software-mansion/react-native-reanimated).
|
|
15
|
-
- **Layout animations** — Animating width/height/layout changes is not supported.
|
|
16
|
-
- **Shared element transitions** — Use Reanimated or React Navigation's shared element transitions.
|
|
17
|
-
- **Old architecture** — Fabric (new architecture) only.
|
|
8
|
+
## Demo
|
|
18
9
|
|
|
19
|
-
|
|
10
|
+

|
|
20
11
|
|
|
21
|
-
|
|
22
|
-
|---|---|
|
|
23
|
-
| Fade in a view | Gesture-driven animations |
|
|
24
|
-
| Slide/translate on state change | Complex interpolations |
|
|
25
|
-
| Scale/rotate on press | Shared values across components |
|
|
26
|
-
| Simple enter animations | Layout animations |
|
|
27
|
-
| You want zero config | You need animation worklets |
|
|
12
|
+
## Getting started
|
|
28
13
|
|
|
29
|
-
|
|
14
|
+
### Installation
|
|
30
15
|
|
|
31
16
|
```bash
|
|
32
17
|
npm install react-native-ease
|
|
@@ -34,7 +19,25 @@ npm install react-native-ease
|
|
|
34
19
|
yarn add react-native-ease
|
|
35
20
|
```
|
|
36
21
|
|
|
37
|
-
|
|
22
|
+
### Migration Skill
|
|
23
|
+
|
|
24
|
+
If you're already using `react-native-reanimated` or React Native's `Animated` API, this project includes an [Agent Skill](https://agentskills.io) that scans your codebase for animations that can be replaced with `react-native-ease` and migrates them automatically.
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx skills add appandflow/react-native-ease
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Then invoke the skill in your agent (e.g., `/react-native-ease-refactor` in Claude Code).
|
|
31
|
+
|
|
32
|
+
The skill will:
|
|
33
|
+
|
|
34
|
+
1. Scan your project for Reanimated/Animated code
|
|
35
|
+
2. Classify which animations can be migrated (and which can't, with reasons)
|
|
36
|
+
3. Show a migration report with before/after details
|
|
37
|
+
4. Let you select which components to migrate
|
|
38
|
+
5. Apply the changes, preserving all non-animation logic
|
|
39
|
+
|
|
40
|
+
### Example
|
|
38
41
|
|
|
39
42
|
```tsx
|
|
40
43
|
import { EaseView } from 'react-native-ease';
|
|
@@ -54,6 +57,32 @@ function FadeCard({ visible, children }) {
|
|
|
54
57
|
|
|
55
58
|
`EaseView` works like a regular `View` — it accepts children, styles, and all standard view props. When values in `animate` change, it smoothly transitions to the new values using native platform animations.
|
|
56
59
|
|
|
60
|
+
## Why
|
|
61
|
+
|
|
62
|
+
### Goals
|
|
63
|
+
|
|
64
|
+
- **Fast** — Animations run entirely on native platform APIs (CAAnimation, ObjectAnimator/SpringAnimation). No JS animation loop, no worklets, no shared values.
|
|
65
|
+
- **Simple** — CSS-transition-like API. Set target values, get smooth animations. One component, a few props.
|
|
66
|
+
- **Lightweight** — Minimal native code, no C++ runtime, no custom animation engine. Just a thin declarative wrapper around what the OS already provides.
|
|
67
|
+
- **Interruptible** — Changing values mid-animation smoothly redirects to the new target. No jumps.
|
|
68
|
+
|
|
69
|
+
### Non-Goals
|
|
70
|
+
|
|
71
|
+
- **Complex gesture-driven animations** — If you need pan/pinch-driven animations, animation worklets, or shared values across components, use [react-native-reanimated](https://github.com/software-mansion/react-native-reanimated).
|
|
72
|
+
- **Layout animations** — Animating width/height/layout changes is not supported.
|
|
73
|
+
- **Shared element transitions** — Use Reanimated or React Navigation's shared element transitions.
|
|
74
|
+
- **Old architecture** — Fabric (new architecture) only.
|
|
75
|
+
|
|
76
|
+
### When to use this vs Reanimated
|
|
77
|
+
|
|
78
|
+
| Use case | Ease | Reanimated |
|
|
79
|
+
| -------------------------------------- | ---- | ---------- |
|
|
80
|
+
| Fade/slide/scale on state change | ✅ | |
|
|
81
|
+
| Enter/exit animations | ✅ | |
|
|
82
|
+
| Gesture-driven animations (pan, pinch) | | ✅ |
|
|
83
|
+
| Layout animations (width, height) | | ✅ |
|
|
84
|
+
| Complex interpolations & chaining | | ✅ |
|
|
85
|
+
|
|
57
86
|
## Guide
|
|
58
87
|
|
|
59
88
|
### Timing Animations
|
|
@@ -67,11 +96,12 @@ Timing animations transition from one value to another over a fixed duration wit
|
|
|
67
96
|
/>
|
|
68
97
|
```
|
|
69
98
|
|
|
70
|
-
| Parameter
|
|
71
|
-
|
|
72
|
-
| `duration` | `number`
|
|
73
|
-
| `easing`
|
|
74
|
-
| `
|
|
99
|
+
| Parameter | Type | Default | Description |
|
|
100
|
+
| ---------- | ------------ | ------------- | ------------------------------------------------------------------------ |
|
|
101
|
+
| `duration` | `number` | `300` | Duration in milliseconds |
|
|
102
|
+
| `easing` | `EasingType` | `'easeInOut'` | Easing curve (preset name or `[x1, y1, x2, y2]` cubic bezier) |
|
|
103
|
+
| `delay` | `number` | `0` | Delay in milliseconds before the animation starts |
|
|
104
|
+
| `loop` | `string` | — | `'repeat'` restarts from the beginning, `'reverse'` alternates direction |
|
|
75
105
|
|
|
76
106
|
Available easing curves:
|
|
77
107
|
|
|
@@ -112,11 +142,12 @@ Spring animations use a physics-based model for natural-feeling motion. Great fo
|
|
|
112
142
|
/>
|
|
113
143
|
```
|
|
114
144
|
|
|
115
|
-
| Parameter
|
|
116
|
-
|
|
117
|
-
| `damping`
|
|
118
|
-
| `stiffness` | `number` | `120`
|
|
119
|
-
| `mass`
|
|
145
|
+
| Parameter | Type | Default | Description |
|
|
146
|
+
| ----------- | -------- | ------- | ------------------------------------------------------------- |
|
|
147
|
+
| `damping` | `number` | `15` | Friction — higher values reduce oscillation |
|
|
148
|
+
| `stiffness` | `number` | `120` | Spring constant — higher values mean faster animation |
|
|
149
|
+
| `mass` | `number` | `1` | Mass of the object — higher values mean slower, more momentum |
|
|
150
|
+
| `delay` | `number` | `0` | Delay in milliseconds before the animation starts |
|
|
120
151
|
|
|
121
152
|
Spring presets for common feels:
|
|
122
153
|
|
|
@@ -147,6 +178,46 @@ Use `{ type: 'none' }` to apply values immediately without animation. Useful for
|
|
|
147
178
|
|
|
148
179
|
`onTransitionEnd` fires immediately with `{ finished: true }`.
|
|
149
180
|
|
|
181
|
+
### Per-Property Transitions
|
|
182
|
+
|
|
183
|
+
Pass a map instead of a single config to use different animation types per property category.
|
|
184
|
+
|
|
185
|
+
```tsx
|
|
186
|
+
<EaseView
|
|
187
|
+
animate={{ opacity: visible ? 1 : 0, translateY: visible ? 0 : 30 }}
|
|
188
|
+
transition={{
|
|
189
|
+
opacity: { type: 'timing', duration: 150, easing: 'easeOut' },
|
|
190
|
+
transform: { type: 'spring', damping: 12, stiffness: 200 },
|
|
191
|
+
}}
|
|
192
|
+
/>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Available category keys:
|
|
196
|
+
|
|
197
|
+
| Key | Properties |
|
|
198
|
+
| ----------------- | ---------------------------------------------------------------- |
|
|
199
|
+
| `default` | Fallback for categories not explicitly listed |
|
|
200
|
+
| `transform` | translateX, translateY, scaleX, scaleY, rotate, rotateX, rotateY |
|
|
201
|
+
| `opacity` | opacity |
|
|
202
|
+
| `borderRadius` | borderRadius |
|
|
203
|
+
| `backgroundColor` | backgroundColor |
|
|
204
|
+
|
|
205
|
+
Use `default` as a fallback for categories not explicitly listed:
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
<EaseView
|
|
209
|
+
animate={{ opacity: 1, scale: 1.2, translateY: -20 }}
|
|
210
|
+
transition={{
|
|
211
|
+
default: { type: 'spring', damping: 15, stiffness: 120 },
|
|
212
|
+
opacity: { type: 'timing', duration: 200, easing: 'easeOut' },
|
|
213
|
+
}}
|
|
214
|
+
/>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
When no `default` key is provided, the library default (timing 300ms easeInOut) applies to all categories.
|
|
218
|
+
|
|
219
|
+
> **Android note:** Android animates `backgroundColor` with `ValueAnimator` (timing only). If a per-property map specifies `type: 'spring'` for `backgroundColor`, it silently falls back to timing 300ms.
|
|
220
|
+
|
|
150
221
|
### Border Radius
|
|
151
222
|
|
|
152
223
|
`borderRadius` can be animated just like other properties. It uses hardware-accelerated platform APIs — `ViewOutlineProvider` + `clipToOutline` on Android and `layer.cornerRadius` + `layer.masksToBounds` on iOS. Unlike RN's style-based `borderRadius` (which uses a Canvas drawable on Android), this clips children properly and is GPU-accelerated.
|
|
@@ -180,6 +251,8 @@ When `borderRadius` is in `animate`, any `borderRadius` in `style` is automatica
|
|
|
180
251
|
|
|
181
252
|
On Android, background color uses `ValueAnimator.ofArgb()` (timing only — spring is not supported for colors). On iOS, it uses `CAAnimation` on the `backgroundColor` layer key path and supports both timing and spring transitions.
|
|
182
253
|
|
|
254
|
+
> **Note:** On Android, background color animation uses `ValueAnimator.ofArgb()` which only supports timing transitions. Spring transitions for `backgroundColor` are not supported on Android and will fall back to timing with the default duration. On iOS, both timing and spring transitions work for background color.
|
|
255
|
+
|
|
183
256
|
When `backgroundColor` is in `animate`, any `backgroundColor` in `style` is automatically stripped to avoid conflicts.
|
|
184
257
|
|
|
185
258
|
### Animatable Properties
|
|
@@ -189,16 +262,16 @@ All properties are set in the `animate` prop as flat values (no transform array)
|
|
|
189
262
|
```tsx
|
|
190
263
|
<EaseView
|
|
191
264
|
animate={{
|
|
192
|
-
opacity: 1,
|
|
193
|
-
translateX: 0,
|
|
194
|
-
translateY: 0,
|
|
195
|
-
scale: 1,
|
|
196
|
-
scaleX: 1,
|
|
197
|
-
scaleY: 1,
|
|
198
|
-
rotate: 0,
|
|
199
|
-
rotateX: 0,
|
|
200
|
-
rotateY: 0,
|
|
201
|
-
borderRadius: 0,
|
|
265
|
+
opacity: 1, // 0 to 1
|
|
266
|
+
translateX: 0, // pixels
|
|
267
|
+
translateY: 0, // pixels
|
|
268
|
+
scale: 1, // 1 = normal size (shorthand for scaleX + scaleY)
|
|
269
|
+
scaleX: 1, // horizontal scale
|
|
270
|
+
scaleY: 1, // vertical scale
|
|
271
|
+
rotate: 0, // Z-axis rotation in degrees
|
|
272
|
+
rotateX: 0, // X-axis rotation in degrees (3D)
|
|
273
|
+
rotateY: 0, // Y-axis rotation in degrees (3D)
|
|
274
|
+
borderRadius: 0, // pixels (hardware-accelerated, clips children)
|
|
202
275
|
backgroundColor: 'transparent', // any RN color value
|
|
203
276
|
}}
|
|
204
277
|
/>
|
|
@@ -245,6 +318,26 @@ Use `initialAnimate` to set starting values. On mount, the view starts at `initi
|
|
|
245
318
|
|
|
246
319
|
Without `initialAnimate`, the view renders at the `animate` values immediately with no animation on mount.
|
|
247
320
|
|
|
321
|
+
### Delay
|
|
322
|
+
|
|
323
|
+
Use `delay` to postpone the start of an animation. This is useful for staggering enter animations across multiple elements.
|
|
324
|
+
|
|
325
|
+
```tsx
|
|
326
|
+
// Staggered fade-in list
|
|
327
|
+
{items.map((item, i) => (
|
|
328
|
+
<EaseView
|
|
329
|
+
key={item.id}
|
|
330
|
+
initialAnimate={{ opacity: 0, translateY: 20 }}
|
|
331
|
+
animate={{ opacity: 1, translateY: 0 }}
|
|
332
|
+
transition={{ type: 'timing', duration: 300, delay: i * 100 }}
|
|
333
|
+
>
|
|
334
|
+
<Text>{item.label}</Text>
|
|
335
|
+
</EaseView>
|
|
336
|
+
))}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
`delay` works with both timing and spring transitions.
|
|
340
|
+
|
|
248
341
|
### Interruption
|
|
249
342
|
|
|
250
343
|
Animations are interruptible by default. If you change `animate` values while an animation is running, it smoothly redirects to the new target from wherever it currently is — no jumping or restarting.
|
|
@@ -280,11 +373,11 @@ By default, scale and rotation animate from the view's center. Use `transformOri
|
|
|
280
373
|
/>
|
|
281
374
|
```
|
|
282
375
|
|
|
283
|
-
| Value
|
|
284
|
-
|
|
285
|
-
| `{ x: 0, y: 0 }`
|
|
376
|
+
| Value | Position |
|
|
377
|
+
| -------------------- | ---------------- |
|
|
378
|
+
| `{ x: 0, y: 0 }` | Top-left |
|
|
286
379
|
| `{ x: 0.5, y: 0.5 }` | Center (default) |
|
|
287
|
-
| `{ x: 1, y: 1 }`
|
|
380
|
+
| `{ x: 1, y: 1 }` | Bottom-right |
|
|
288
381
|
|
|
289
382
|
### Style Handling
|
|
290
383
|
|
|
@@ -318,32 +411,32 @@ By default, scale and rotation animate from the view's center. Use `transformOri
|
|
|
318
411
|
|
|
319
412
|
A `View` that animates property changes using native platform APIs.
|
|
320
413
|
|
|
321
|
-
| Prop
|
|
322
|
-
|
|
323
|
-
| `animate`
|
|
324
|
-
| `initialAnimate`
|
|
325
|
-
| `transition`
|
|
326
|
-
| `onTransitionEnd`
|
|
327
|
-
| `transformOrigin`
|
|
328
|
-
| `useHardwareLayer` | `boolean`
|
|
329
|
-
| `style`
|
|
330
|
-
| `children`
|
|
331
|
-
| ...rest
|
|
414
|
+
| Prop | Type | Description |
|
|
415
|
+
| ------------------ | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
416
|
+
| `animate` | `AnimateProps` | Target values for animated properties |
|
|
417
|
+
| `initialAnimate` | `AnimateProps` | Starting values for enter animations (animates to `animate` on mount) |
|
|
418
|
+
| `transition` | `Transition` | Animation configuration (timing, spring, or none) |
|
|
419
|
+
| `onTransitionEnd` | `(event) => void` | Called when all animations complete with `{ finished: boolean }` |
|
|
420
|
+
| `transformOrigin` | `{ x?: number; y?: number }` | Pivot point for scale/rotation as 0–1 fractions. Default: `{ x: 0.5, y: 0.5 }` (center) |
|
|
421
|
+
| `useHardwareLayer` | `boolean` | Android only — rasterize to GPU texture during animations. See [Hardware Layers](#hardware-layers-android). Default: `false` |
|
|
422
|
+
| `style` | `ViewStyle` | Non-animated styles (layout, colors, borders, etc.) |
|
|
423
|
+
| `children` | `ReactNode` | Child elements |
|
|
424
|
+
| ...rest | `ViewProps` | All other standard View props |
|
|
332
425
|
|
|
333
426
|
### `AnimateProps`
|
|
334
427
|
|
|
335
|
-
| Property
|
|
336
|
-
|
|
337
|
-
| `opacity`
|
|
338
|
-
| `translateX`
|
|
339
|
-
| `translateY`
|
|
340
|
-
| `scale`
|
|
341
|
-
| `scaleX`
|
|
342
|
-
| `scaleY`
|
|
343
|
-
| `rotate`
|
|
344
|
-
| `rotateX`
|
|
345
|
-
| `rotateY`
|
|
346
|
-
| `borderRadius`
|
|
428
|
+
| Property | Type | Default | Description |
|
|
429
|
+
| ----------------- | ------------ | --------------- | ------------------------------------------------------------------------------------ |
|
|
430
|
+
| `opacity` | `number` | `1` | View opacity (0–1) |
|
|
431
|
+
| `translateX` | `number` | `0` | Horizontal translation in pixels |
|
|
432
|
+
| `translateY` | `number` | `0` | Vertical translation in pixels |
|
|
433
|
+
| `scale` | `number` | `1` | Uniform scale factor (shorthand for `scaleX` + `scaleY`) |
|
|
434
|
+
| `scaleX` | `number` | `1` | Horizontal scale factor (overrides `scale` for X axis) |
|
|
435
|
+
| `scaleY` | `number` | `1` | Vertical scale factor (overrides `scale` for Y axis) |
|
|
436
|
+
| `rotate` | `number` | `0` | Z-axis rotation in degrees |
|
|
437
|
+
| `rotateX` | `number` | `0` | X-axis rotation in degrees (3D) |
|
|
438
|
+
| `rotateY` | `number` | `0` | Y-axis rotation in degrees (3D) |
|
|
439
|
+
| `borderRadius` | `number` | `0` | Border radius in pixels (hardware-accelerated, clips children) |
|
|
347
440
|
| `backgroundColor` | `ColorValue` | `'transparent'` | Background color (any RN color value). Timing-only on Android, spring+timing on iOS. |
|
|
348
441
|
|
|
349
442
|
Properties not specified in `animate` default to their identity values.
|
|
@@ -355,6 +448,7 @@ Properties not specified in `animate` default to their identity values.
|
|
|
355
448
|
type: 'timing';
|
|
356
449
|
duration?: number; // default: 300 (ms)
|
|
357
450
|
easing?: EasingType; // default: 'easeInOut' — preset name or [x1, y1, x2, y2]
|
|
451
|
+
delay?: number; // default: 0 (ms)
|
|
358
452
|
loop?: 'repeat' | 'reverse'; // default: none
|
|
359
453
|
}
|
|
360
454
|
```
|
|
@@ -367,6 +461,7 @@ Properties not specified in `animate` default to their identity values.
|
|
|
367
461
|
damping?: number; // default: 15
|
|
368
462
|
stiffness?: number; // default: 120
|
|
369
463
|
mass?: number; // default: 1
|
|
464
|
+
delay?: number; // default: 0 (ms)
|
|
370
465
|
}
|
|
371
466
|
```
|
|
372
467
|
|
|
@@ -385,10 +480,7 @@ Applies values instantly with no animation. `onTransitionEnd` fires immediately
|
|
|
385
480
|
Setting `useHardwareLayer` rasterizes the view into a GPU texture for the duration of the animation. This means animated property changes (opacity, scale, rotation) are composited on the RenderThread without redrawing the view hierarchy — useful for complex views with many children.
|
|
386
481
|
|
|
387
482
|
```tsx
|
|
388
|
-
<EaseView
|
|
389
|
-
animate={{ opacity: isVisible ? 1 : 0 }}
|
|
390
|
-
useHardwareLayer
|
|
391
|
-
/>
|
|
483
|
+
<EaseView animate={{ opacity: isVisible ? 1 : 0 }} useHardwareLayer />
|
|
392
484
|
```
|
|
393
485
|
|
|
394
486
|
**Trade-offs:**
|
|
@@ -399,6 +491,58 @@ Setting `useHardwareLayer` rasterizes the view into a GPU texture for the durati
|
|
|
399
491
|
|
|
400
492
|
No-op on iOS where Core Animation already composites off the main thread.
|
|
401
493
|
|
|
494
|
+
## Benchmarks
|
|
495
|
+
|
|
496
|
+
The example app includes a benchmark that measures per-frame animation overhead across different approaches. All approaches run the same animation (translateX loop, linear, 2s) on a configurable number of views.
|
|
497
|
+
|
|
498
|
+
### Android (release build, emulator, M4 MacBook Pro)
|
|
499
|
+
|
|
500
|
+
UI thread time per frame: anim + layout + draw (ms). Lower is better.
|
|
501
|
+
|
|
502
|
+

|
|
503
|
+
|
|
504
|
+
<details>
|
|
505
|
+
<summary>Detailed numbers</summary>
|
|
506
|
+
|
|
507
|
+
| Views | Metric | Ease | Reanimated SV | Reanimated SV (FF) | Reanimated CSS | Reanimated CSS (FF) | RN Animated |
|
|
508
|
+
|-------|--------|------|---------------|---------------------|----------------|----------------------|-------------|
|
|
509
|
+
| 10 | Avg | 0.21 | 1.15 | 0.75 | 0.99 | 0.45 | 0.36 |
|
|
510
|
+
| 10 | P95 | 0.33 | 1.70 | 1.53 | 1.44 | 0.80 | 0.62 |
|
|
511
|
+
| 10 | P99 | 0.48 | 1.94 | 2.26 | 1.62 | 1.35 | 0.98 |
|
|
512
|
+
| 100 | Avg | 0.36 | 2.71 | 1.81 | 2.19 | 1.01 | 0.71 |
|
|
513
|
+
| 100 | P95 | 0.56 | 3.09 | 2.29 | 2.67 | 1.91 | 1.08 |
|
|
514
|
+
| 100 | P99 | 0.71 | 3.20 | 2.63 | 2.97 | 2.25 | 1.36 |
|
|
515
|
+
| 500 | Avg | 0.60 | 8.31 | 5.37 | 5.50 | 2.37 | 1.60 |
|
|
516
|
+
| 500 | P95 | 0.75 | 9.26 | 6.36 | 6.34 | 2.86 | 1.88 |
|
|
517
|
+
| 500 | P99 | 0.87 | 9.59 | 6.89 | 6.88 | 3.22 | 3.84 |
|
|
518
|
+
|
|
519
|
+
</details>
|
|
520
|
+
|
|
521
|
+
### iOS (release build, simulator, iPhone 16 Pro, M4 MacBook Pro)
|
|
522
|
+
|
|
523
|
+
Display link callback time per frame (ms). Lower is better.
|
|
524
|
+
|
|
525
|
+

|
|
526
|
+
|
|
527
|
+
<details>
|
|
528
|
+
<summary>Detailed numbers</summary>
|
|
529
|
+
|
|
530
|
+
| Views | Metric | Ease | Reanimated SV | Reanimated SV (FF) | Reanimated CSS | Reanimated CSS (FF) | RN Animated |
|
|
531
|
+
|-------|--------|------|---------------|---------------------|----------------|----------------------|-------------|
|
|
532
|
+
| 10 | Avg | 0.01 | 1.33 | 1.08 | 1.06 | 0.63 | 0.83 |
|
|
533
|
+
| 10 | P95 | 0.02 | 1.67 | 1.59 | 1.34 | 1.01 | 1.18 |
|
|
534
|
+
| 10 | P99 | 0.03 | 1.90 | 1.68 | 1.50 | 1.08 | 1.31 |
|
|
535
|
+
| 100 | Avg | 0.01 | 3.72 | 3.33 | 2.71 | 2.48 | 3.32 |
|
|
536
|
+
| 100 | P95 | 0.01 | 5.21 | 4.50 | 3.83 | 3.39 | 4.28 |
|
|
537
|
+
| 100 | P99 | 0.02 | 5.68 | 4.75 | 4.91 | 3.79 | 4.55 |
|
|
538
|
+
| 500 | Avg | 0.01 | 6.84 | 6.54 | 4.16 | 3.70 | 4.91 |
|
|
539
|
+
| 500 | P95 | 0.01 | 7.69 | 7.32 | 4.59 | 4.22 | 5.66 |
|
|
540
|
+
| 500 | P99 | 0.02 | 8.10 | 7.45 | 4.71 | 4.33 | 5.89 |
|
|
541
|
+
|
|
542
|
+
</details>
|
|
543
|
+
|
|
544
|
+
Ease stays near zero because animations run entirely on platform APIs. On iOS, Core Animation runs on a separate render server process off the main thread, which is why Ease shows ~0ms. On Android, ObjectAnimator runs on the UI thread but is significantly lighter than other approaches. Reanimated results shown with experimental [feature flags](https://docs.swmansion.com/react-native-reanimated/docs/guides/feature-flags/) OFF (default) and ON (FF). Run the benchmark yourself in the [example app](example/).
|
|
545
|
+
|
|
402
546
|
## How It Works
|
|
403
547
|
|
|
404
548
|
`EaseView` is a native Fabric component. The JS side flattens your `animate` and `transition` props into flat native props. When those props change, the native view:
|