react-native-ease 0.1.0-alpha.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/Ease.podspec +20 -0
- package/LICENSE +20 -0
- package/README.md +411 -0
- package/android/build.gradle +68 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/ease/EasePackage.kt +17 -0
- package/android/src/main/java/com/ease/EaseView.kt +541 -0
- package/android/src/main/java/com/ease/EaseViewManager.kt +233 -0
- package/ios/EaseView.h +14 -0
- package/ios/EaseView.mm +435 -0
- package/lib/module/EaseView.js +186 -0
- package/lib/module/EaseView.js.map +1 -0
- package/lib/module/EaseViewNativeComponent.ts +68 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/EaseView.d.ts +33 -0
- package/lib/typescript/src/EaseView.d.ts.map +1 -0
- package/lib/typescript/src/EaseViewNativeComponent.d.ts +38 -0
- package/lib/typescript/src/EaseViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +4 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +66 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +187 -0
- package/src/EaseView.tsx +256 -0
- package/src/EaseViewNativeComponent.ts +68 -0
- package/src/index.tsx +13 -0
- package/src/types.ts +78 -0
package/Ease.podspec
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "Ease"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
14
|
+
s.source = { :git => "https://github.com/janicduplessis/react-native-ease.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
|
|
17
|
+
s.private_header_files = "ios/**/*.h"
|
|
18
|
+
|
|
19
|
+
install_modules_dependencies(s)
|
|
20
|
+
end
|
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Janic Duplessis
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
# 🍃 react-native-ease
|
|
2
|
+
|
|
3
|
+
Lightweight declarative animations powered by platform APIs. Uses Core Animation on iOS and Animator on Android — zero JS overhead.
|
|
4
|
+
|
|
5
|
+
## Goals
|
|
6
|
+
|
|
7
|
+
- **Fast** — Animations run entirely on native platform APIs (CAAnimation, ObjectAnimator/SpringAnimation). No JS animation loop, no worklets, no shared values.
|
|
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.
|
|
18
|
+
|
|
19
|
+
## When to use this vs Reanimated
|
|
20
|
+
|
|
21
|
+
| Use react-native-ease | Use Reanimated |
|
|
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 |
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install react-native-ease
|
|
33
|
+
# or
|
|
34
|
+
yarn add react-native-ease
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import { EaseView } from 'react-native-ease';
|
|
41
|
+
|
|
42
|
+
function FadeCard({ visible, children }) {
|
|
43
|
+
return (
|
|
44
|
+
<EaseView
|
|
45
|
+
animate={{ opacity: visible ? 1 : 0 }}
|
|
46
|
+
transition={{ type: 'timing', duration: 300 }}
|
|
47
|
+
style={styles.card}
|
|
48
|
+
>
|
|
49
|
+
{children}
|
|
50
|
+
</EaseView>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`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
|
+
|
|
57
|
+
## Guide
|
|
58
|
+
|
|
59
|
+
### Timing Animations
|
|
60
|
+
|
|
61
|
+
Timing animations transition from one value to another over a fixed duration with an easing curve.
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
<EaseView
|
|
65
|
+
animate={{ opacity: isVisible ? 1 : 0 }}
|
|
66
|
+
transition={{ type: 'timing', duration: 300, easing: 'easeOut' }}
|
|
67
|
+
/>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
| Parameter | Type | Default | Description |
|
|
71
|
+
|---|---|---|---|
|
|
72
|
+
| `duration` | `number` | `300` | Duration in milliseconds |
|
|
73
|
+
| `easing` | `EasingType` | `'easeInOut'` | Easing curve (preset name or `[x1, y1, x2, y2]` cubic bezier) |
|
|
74
|
+
| `loop` | `string` | — | `'repeat'` restarts from the beginning, `'reverse'` alternates direction |
|
|
75
|
+
|
|
76
|
+
Available easing curves:
|
|
77
|
+
|
|
78
|
+
- `'linear'` — constant speed
|
|
79
|
+
- `'easeIn'` — starts slow, accelerates
|
|
80
|
+
- `'easeOut'` — starts fast, decelerates
|
|
81
|
+
- `'easeInOut'` — slow start and end, fast middle
|
|
82
|
+
- `[x1, y1, x2, y2]` — custom cubic bezier (same as CSS `cubic-bezier()`)
|
|
83
|
+
|
|
84
|
+
### Custom Easing
|
|
85
|
+
|
|
86
|
+
Pass a `[x1, y1, x2, y2]` tuple for custom cubic bezier curves. The values correspond to the two control points of the bezier curve, matching the CSS `cubic-bezier()` function.
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
// Standard Material Design easing
|
|
90
|
+
<EaseView
|
|
91
|
+
animate={{ opacity: isVisible ? 1 : 0 }}
|
|
92
|
+
transition={{ type: 'timing', duration: 300, easing: [0.4, 0, 0.2, 1] }}
|
|
93
|
+
/>
|
|
94
|
+
|
|
95
|
+
// Overshoot (y-values can exceed 0–1)
|
|
96
|
+
<EaseView
|
|
97
|
+
animate={{ scale: active ? 1.2 : 1 }}
|
|
98
|
+
transition={{ type: 'timing', duration: 500, easing: [0.68, -0.55, 0.265, 1.55] }}
|
|
99
|
+
/>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
x-values (x1, x2) must be between 0 and 1. y-values can exceed this range to create overshoot effects.
|
|
103
|
+
|
|
104
|
+
### Spring Animations
|
|
105
|
+
|
|
106
|
+
Spring animations use a physics-based model for natural-feeling motion. Great for interactive elements.
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
<EaseView
|
|
110
|
+
animate={{ translateX: isOpen ? 200 : 0 }}
|
|
111
|
+
transition={{ type: 'spring', damping: 15, stiffness: 120, mass: 1 }}
|
|
112
|
+
/>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
| Parameter | Type | Default | Description |
|
|
116
|
+
|---|---|---|---|
|
|
117
|
+
| `damping` | `number` | `15` | Friction — higher values reduce oscillation |
|
|
118
|
+
| `stiffness` | `number` | `120` | Spring constant — higher values mean faster animation |
|
|
119
|
+
| `mass` | `number` | `1` | Mass of the object — higher values mean slower, more momentum |
|
|
120
|
+
|
|
121
|
+
Spring presets for common feels:
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
// Snappy (no bounce)
|
|
125
|
+
{ type: 'spring', damping: 20, stiffness: 300, mass: 1 }
|
|
126
|
+
|
|
127
|
+
// Gentle bounce
|
|
128
|
+
{ type: 'spring', damping: 12, stiffness: 120, mass: 1 }
|
|
129
|
+
|
|
130
|
+
// Bouncy
|
|
131
|
+
{ type: 'spring', damping: 8, stiffness: 200, mass: 1 }
|
|
132
|
+
|
|
133
|
+
// Slow and heavy
|
|
134
|
+
{ type: 'spring', damping: 20, stiffness: 60, mass: 2 }
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Disabling Animations
|
|
138
|
+
|
|
139
|
+
Use `{ type: 'none' }` to apply values immediately without animation. Useful for skipping animations in reduced-motion modes or when you need an instant state change.
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
<EaseView
|
|
143
|
+
animate={{ opacity: isVisible ? 1 : 0 }}
|
|
144
|
+
transition={{ type: 'none' }}
|
|
145
|
+
/>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`onTransitionEnd` fires immediately with `{ finished: true }`.
|
|
149
|
+
|
|
150
|
+
### Border Radius
|
|
151
|
+
|
|
152
|
+
`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.
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
<EaseView
|
|
156
|
+
animate={{ borderRadius: expanded ? 0 : 16 }}
|
|
157
|
+
transition={{ type: 'timing', duration: 300 }}
|
|
158
|
+
style={styles.card}
|
|
159
|
+
>
|
|
160
|
+
<Image source={heroImage} style={styles.image} />
|
|
161
|
+
<Text>Content is clipped to rounded corners</Text>
|
|
162
|
+
</EaseView>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
When `borderRadius` is in `animate`, any `borderRadius` in `style` is automatically stripped to avoid conflicts.
|
|
166
|
+
|
|
167
|
+
### Animatable Properties
|
|
168
|
+
|
|
169
|
+
All properties are set in the `animate` prop as flat values (no transform array).
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
<EaseView
|
|
173
|
+
animate={{
|
|
174
|
+
opacity: 1, // 0 to 1
|
|
175
|
+
translateX: 0, // pixels
|
|
176
|
+
translateY: 0, // pixels
|
|
177
|
+
scale: 1, // 1 = normal size (shorthand for scaleX + scaleY)
|
|
178
|
+
scaleX: 1, // horizontal scale
|
|
179
|
+
scaleY: 1, // vertical scale
|
|
180
|
+
rotate: 0, // Z-axis rotation in degrees
|
|
181
|
+
rotateX: 0, // X-axis rotation in degrees (3D)
|
|
182
|
+
rotateY: 0, // Y-axis rotation in degrees (3D)
|
|
183
|
+
borderRadius: 0, // pixels (hardware-accelerated, clips children)
|
|
184
|
+
}}
|
|
185
|
+
/>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`scale` is a shorthand that sets both `scaleX` and `scaleY`. When `scaleX` or `scaleY` is also specified, it overrides the `scale` value for that axis.
|
|
189
|
+
|
|
190
|
+
You can animate any combination of properties simultaneously. All properties share the same transition config.
|
|
191
|
+
|
|
192
|
+
### Looping Animations
|
|
193
|
+
|
|
194
|
+
Timing animations can loop infinitely. Use `'repeat'` to restart from the beginning or `'reverse'` to alternate direction.
|
|
195
|
+
|
|
196
|
+
```tsx
|
|
197
|
+
// Pulsing opacity
|
|
198
|
+
<EaseView
|
|
199
|
+
initialAnimate={{ opacity: 0.3 }}
|
|
200
|
+
animate={{ opacity: 1 }}
|
|
201
|
+
transition={{ type: 'timing', duration: 1000, easing: 'easeInOut', loop: 'reverse' }}
|
|
202
|
+
/>
|
|
203
|
+
|
|
204
|
+
// Marquee-style scroll
|
|
205
|
+
<EaseView
|
|
206
|
+
initialAnimate={{ translateX: 0 }}
|
|
207
|
+
animate={{ translateX: -300 }}
|
|
208
|
+
transition={{ type: 'timing', duration: 3000, easing: 'linear', loop: 'repeat' }}
|
|
209
|
+
/>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Loop requires `initialAnimate` to define the starting value. Spring animations do not support looping.
|
|
213
|
+
|
|
214
|
+
### Enter Animations
|
|
215
|
+
|
|
216
|
+
Use `initialAnimate` to set starting values. On mount, the view starts at `initialAnimate` values and animates to `animate` values.
|
|
217
|
+
|
|
218
|
+
```tsx
|
|
219
|
+
// Fade in and slide up on mount
|
|
220
|
+
<EaseView
|
|
221
|
+
initialAnimate={{ opacity: 0, translateY: 20 }}
|
|
222
|
+
animate={{ opacity: 1, translateY: 0 }}
|
|
223
|
+
transition={{ type: 'spring', damping: 15, stiffness: 120, mass: 1 }}
|
|
224
|
+
/>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Without `initialAnimate`, the view renders at the `animate` values immediately with no animation on mount.
|
|
228
|
+
|
|
229
|
+
### Interruption
|
|
230
|
+
|
|
231
|
+
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.
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
// Rapidly toggling this is fine — each toggle smoothly
|
|
235
|
+
// redirects the animation from its current position
|
|
236
|
+
<EaseView
|
|
237
|
+
animate={{ translateX: isLeft ? 0 : 200 }}
|
|
238
|
+
transition={{ type: 'spring', damping: 15, stiffness: 120, mass: 1 }}
|
|
239
|
+
/>
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Transform Origin
|
|
243
|
+
|
|
244
|
+
By default, scale and rotation animate from the view's center. Use `transformOrigin` to change the pivot point with 0–1 fractions.
|
|
245
|
+
|
|
246
|
+
```tsx
|
|
247
|
+
// Rotate from top-left corner
|
|
248
|
+
<EaseView
|
|
249
|
+
animate={{ rotate: isOpen ? 45 : 0 }}
|
|
250
|
+
transformOrigin={{ x: 0, y: 0 }}
|
|
251
|
+
transition={{ type: 'spring', damping: 12, stiffness: 200, mass: 1 }}
|
|
252
|
+
style={styles.card}
|
|
253
|
+
/>
|
|
254
|
+
|
|
255
|
+
// Scale from bottom-right
|
|
256
|
+
<EaseView
|
|
257
|
+
animate={{ scale: active ? 1.2 : 1 }}
|
|
258
|
+
transformOrigin={{ x: 1, y: 1 }}
|
|
259
|
+
transition={{ type: 'spring', damping: 15, stiffness: 120, mass: 1 }}
|
|
260
|
+
style={styles.card}
|
|
261
|
+
/>
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
| Value | Position |
|
|
265
|
+
|---|---|
|
|
266
|
+
| `{ x: 0, y: 0 }` | Top-left |
|
|
267
|
+
| `{ x: 0.5, y: 0.5 }` | Center (default) |
|
|
268
|
+
| `{ x: 1, y: 1 }` | Bottom-right |
|
|
269
|
+
|
|
270
|
+
### Style Handling
|
|
271
|
+
|
|
272
|
+
`EaseView` accepts all standard `ViewStyle` properties. If a property appears in both `style` and `animate`, the animated value takes priority and the style value is stripped. A dev warning is logged when this happens.
|
|
273
|
+
|
|
274
|
+
```tsx
|
|
275
|
+
// opacity in style works because only translateY is animated
|
|
276
|
+
<EaseView
|
|
277
|
+
animate={{ translateY: moved ? -10 : 0 }}
|
|
278
|
+
transition={{ type: 'spring', damping: 15, stiffness: 120, mass: 1 }}
|
|
279
|
+
style={{
|
|
280
|
+
opacity: 0.9,
|
|
281
|
+
backgroundColor: 'white',
|
|
282
|
+
borderRadius: 16,
|
|
283
|
+
padding: 16,
|
|
284
|
+
}}
|
|
285
|
+
>
|
|
286
|
+
<Text>Notification card</Text>
|
|
287
|
+
</EaseView>
|
|
288
|
+
|
|
289
|
+
// ⚠️ opacity is in both — animate wins, style opacity is stripped, dev warning logged
|
|
290
|
+
<EaseView
|
|
291
|
+
animate={{ opacity: 1 }}
|
|
292
|
+
style={{ opacity: 0.5, backgroundColor: 'white' }}
|
|
293
|
+
/>
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## API Reference
|
|
297
|
+
|
|
298
|
+
### `<EaseView>`
|
|
299
|
+
|
|
300
|
+
A `View` that animates property changes using native platform APIs.
|
|
301
|
+
|
|
302
|
+
| Prop | Type | Description |
|
|
303
|
+
|---|---|---|
|
|
304
|
+
| `animate` | `AnimateProps` | Target values for animated properties |
|
|
305
|
+
| `initialAnimate` | `AnimateProps` | Starting values for enter animations (animates to `animate` on mount) |
|
|
306
|
+
| `transition` | `Transition` | Animation configuration (timing, spring, or none) |
|
|
307
|
+
| `onTransitionEnd` | `(event) => void` | Called when all animations complete with `{ finished: boolean }` |
|
|
308
|
+
| `transformOrigin` | `{ x?: number; y?: number }` | Pivot point for scale/rotation as 0–1 fractions. Default: `{ x: 0.5, y: 0.5 }` (center) |
|
|
309
|
+
| `useHardwareLayer` | `boolean` | Android only — rasterize to GPU texture during animations. See [Hardware Layers](#hardware-layers-android). Default: `false` |
|
|
310
|
+
| `style` | `ViewStyle` | Non-animated styles (layout, colors, borders, etc.) |
|
|
311
|
+
| `children` | `ReactNode` | Child elements |
|
|
312
|
+
| ...rest | `ViewProps` | All other standard View props |
|
|
313
|
+
|
|
314
|
+
### `AnimateProps`
|
|
315
|
+
|
|
316
|
+
| Property | Type | Default | Description |
|
|
317
|
+
|---|---|---|---|
|
|
318
|
+
| `opacity` | `number` | `1` | View opacity (0–1) |
|
|
319
|
+
| `translateX` | `number` | `0` | Horizontal translation in pixels |
|
|
320
|
+
| `translateY` | `number` | `0` | Vertical translation in pixels |
|
|
321
|
+
| `scale` | `number` | `1` | Uniform scale factor (shorthand for `scaleX` + `scaleY`) |
|
|
322
|
+
| `scaleX` | `number` | `1` | Horizontal scale factor (overrides `scale` for X axis) |
|
|
323
|
+
| `scaleY` | `number` | `1` | Vertical scale factor (overrides `scale` for Y axis) |
|
|
324
|
+
| `rotate` | `number` | `0` | Z-axis rotation in degrees |
|
|
325
|
+
| `rotateX` | `number` | `0` | X-axis rotation in degrees (3D) |
|
|
326
|
+
| `rotateY` | `number` | `0` | Y-axis rotation in degrees (3D) |
|
|
327
|
+
| `borderRadius` | `number` | `0` | Border radius in pixels (hardware-accelerated, clips children) |
|
|
328
|
+
|
|
329
|
+
Properties not specified in `animate` default to their identity values.
|
|
330
|
+
|
|
331
|
+
### `TimingTransition`
|
|
332
|
+
|
|
333
|
+
```tsx
|
|
334
|
+
{
|
|
335
|
+
type: 'timing';
|
|
336
|
+
duration?: number; // default: 300 (ms)
|
|
337
|
+
easing?: EasingType; // default: 'easeInOut' — preset name or [x1, y1, x2, y2]
|
|
338
|
+
loop?: 'repeat' | 'reverse'; // default: none
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### `SpringTransition`
|
|
343
|
+
|
|
344
|
+
```tsx
|
|
345
|
+
{
|
|
346
|
+
type: 'spring';
|
|
347
|
+
damping?: number; // default: 15
|
|
348
|
+
stiffness?: number; // default: 120
|
|
349
|
+
mass?: number; // default: 1
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### `NoneTransition`
|
|
354
|
+
|
|
355
|
+
```tsx
|
|
356
|
+
{
|
|
357
|
+
type: 'none';
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Applies values instantly with no animation. `onTransitionEnd` fires immediately with `{ finished: true }`.
|
|
362
|
+
|
|
363
|
+
## Hardware Layers (Android)
|
|
364
|
+
|
|
365
|
+
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.
|
|
366
|
+
|
|
367
|
+
```tsx
|
|
368
|
+
<EaseView
|
|
369
|
+
animate={{ opacity: isVisible ? 1 : 0 }}
|
|
370
|
+
useHardwareLayer
|
|
371
|
+
/>
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
**Trade-offs:**
|
|
375
|
+
|
|
376
|
+
- Faster rendering for opacity, scale, and rotation animations (RenderThread compositing).
|
|
377
|
+
- Uses additional GPU memory for the off-screen texture (proportional to view size).
|
|
378
|
+
- Children that overflow the view's layout bounds are **clipped** by the texture. This causes visual artifacts when animating `translateX`/`translateY` on views with overflowing content.
|
|
379
|
+
|
|
380
|
+
No-op on iOS where Core Animation already composites off the main thread.
|
|
381
|
+
|
|
382
|
+
## How It Works
|
|
383
|
+
|
|
384
|
+
`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:
|
|
385
|
+
|
|
386
|
+
1. **Diffs** previous vs new values to find what changed
|
|
387
|
+
2. **Reads** the current in-flight value (for smooth interruption)
|
|
388
|
+
3. **Creates** a platform-native animation from the current value to the new target
|
|
389
|
+
4. **Sets** the final value immediately on the model layer
|
|
390
|
+
|
|
391
|
+
On iOS, this uses `CABasicAnimation`/`CASpringAnimation` on `CALayer` key paths. On Android, this uses `ObjectAnimator`/`SpringAnimation` on `View` properties. No JS thread involvement during the animation.
|
|
392
|
+
|
|
393
|
+
## Requirements
|
|
394
|
+
|
|
395
|
+
- React Native 0.76+ (new architecture / Fabric)
|
|
396
|
+
- iOS 15.1+
|
|
397
|
+
- Android minSdk 24+
|
|
398
|
+
|
|
399
|
+
## Contributing
|
|
400
|
+
|
|
401
|
+
- [Development workflow](CONTRIBUTING.md#development-workflow)
|
|
402
|
+
- [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
|
|
403
|
+
- [Code of conduct](CODE_OF_CONDUCT.md)
|
|
404
|
+
|
|
405
|
+
## License
|
|
406
|
+
|
|
407
|
+
MIT
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.Ease = [
|
|
3
|
+
kotlinVersion: "2.0.21",
|
|
4
|
+
minSdkVersion: 24,
|
|
5
|
+
compileSdkVersion: 36,
|
|
6
|
+
targetSdkVersion: 36
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
ext.getExtOrDefault = { prop ->
|
|
10
|
+
if (rootProject.ext.has(prop)) {
|
|
11
|
+
return rootProject.ext.get(prop)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return Ease[prop]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
repositories {
|
|
18
|
+
google()
|
|
19
|
+
mavenCentral()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
dependencies {
|
|
23
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
24
|
+
// noinspection DifferentKotlinGradleVersion
|
|
25
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
apply plugin: "com.android.library"
|
|
31
|
+
apply plugin: "kotlin-android"
|
|
32
|
+
|
|
33
|
+
apply plugin: "com.facebook.react"
|
|
34
|
+
|
|
35
|
+
android {
|
|
36
|
+
namespace "com.ease"
|
|
37
|
+
|
|
38
|
+
compileSdkVersion getExtOrDefault("compileSdkVersion")
|
|
39
|
+
|
|
40
|
+
defaultConfig {
|
|
41
|
+
minSdkVersion getExtOrDefault("minSdkVersion")
|
|
42
|
+
targetSdkVersion getExtOrDefault("targetSdkVersion")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
buildFeatures {
|
|
46
|
+
buildConfig true
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
buildTypes {
|
|
50
|
+
release {
|
|
51
|
+
minifyEnabled false
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
lint {
|
|
56
|
+
disable "GradleCompatible"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
compileOptions {
|
|
60
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
61
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
dependencies {
|
|
66
|
+
implementation "com.facebook.react:react-android"
|
|
67
|
+
implementation "androidx.dynamicanimation:dynamicanimation:1.0.0"
|
|
68
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
package com.ease
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
7
|
+
import com.facebook.react.uimanager.ViewManager
|
|
8
|
+
|
|
9
|
+
class EaseViewPackage : BaseReactPackage() {
|
|
10
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
11
|
+
return listOf(EaseViewManager())
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? = null
|
|
15
|
+
|
|
16
|
+
override fun getReactModuleInfoProvider() = ReactModuleInfoProvider { emptyMap() }
|
|
17
|
+
}
|