Skip to content

Commit 5b067d2

Browse files
feat: implement EaseView with native animations
Declarative animation component using Core Animation (iOS) and ObjectAnimator/SpringAnimation (Android). Supports opacity, translate, scale, and rotate with timing and spring transitions.
1 parent 79eae81 commit 5b067d2

15 files changed

Lines changed: 13131 additions & 80 deletions

File tree

README.md

Lines changed: 235 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,252 @@
1-
# react-native-ease
1+
# 🍃 react-native-ease
22

3-
Lightweight declarative animations powered by platform APIs
3+
Lightweight declarative animations powered by platform APIs. Uses Core Animation on iOS and Animator on Android — zero JS thread overhead.
44

5-
## Installation
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+
- **Fine-grained control** — No per-property transition config, no completion callbacks, no animation chaining. If you need that level of control, Reanimated is the right tool.
619

20+
## When to use this vs Reanimated
721

8-
```sh
22+
| Use react-native-ease | Use Reanimated |
23+
|---|---|
24+
| Fade in a view | Gesture-driven animations |
25+
| Slide/translate on state change | Complex interpolations |
26+
| Scale/rotate on press | Shared values across components |
27+
| Simple enter animations | Layout animations |
28+
| You want zero config | You need animation worklets |
29+
30+
## Installation
31+
32+
```bash
933
npm install react-native-ease
34+
# or
35+
yarn add react-native-ease
36+
```
37+
38+
On iOS, run pod install:
39+
40+
```bash
41+
cd ios && pod install
42+
```
43+
44+
## Quick Start
45+
46+
```tsx
47+
import { EaseView } from 'react-native-ease';
48+
49+
function FadeCard({ visible, children }) {
50+
return (
51+
<EaseView
52+
animate={{ opacity: visible ? 1 : 0 }}
53+
transition={{ type: 'timing', duration: 300 }}
54+
style={styles.card}
55+
>
56+
{children}
57+
</EaseView>
58+
);
59+
}
60+
```
61+
62+
`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.
63+
64+
## Guide
65+
66+
### Timing Animations
67+
68+
Timing animations transition from one value to another over a fixed duration with an easing curve.
69+
70+
```tsx
71+
<EaseView
72+
animate={{ opacity: isVisible ? 1 : 0 }}
73+
transition={{ type: 'timing', duration: 300, easing: 'easeOut' }}
74+
/>
75+
```
76+
77+
| Parameter | Type | Default | Description |
78+
|---|---|---|---|
79+
| `duration` | `number` | `300` | Duration in milliseconds |
80+
| `easing` | `string` | `'easeInOut'` | Easing curve |
81+
82+
Available easing curves:
83+
84+
- `'linear'` — constant speed
85+
- `'easeIn'` — starts slow, accelerates
86+
- `'easeOut'` — starts fast, decelerates
87+
- `'easeInOut'` — slow start and end, fast middle
88+
89+
### Spring Animations
90+
91+
Spring animations use a physics-based model for natural-feeling motion. Great for interactive elements.
92+
93+
```tsx
94+
<EaseView
95+
animate={{ translateX: isOpen ? 200 : 0 }}
96+
transition={{ type: 'spring', damping: 15, stiffness: 120, mass: 1 }}
97+
/>
98+
```
99+
100+
| Parameter | Type | Default | Description |
101+
|---|---|---|---|
102+
| `damping` | `number` | `15` | Friction — higher values reduce oscillation |
103+
| `stiffness` | `number` | `120` | Spring constant — higher values mean faster animation |
104+
| `mass` | `number` | `1` | Mass of the object — higher values mean slower, more momentum |
105+
106+
Spring presets for common feels:
107+
108+
```tsx
109+
// Snappy (no bounce)
110+
{ type: 'spring', damping: 20, stiffness: 300, mass: 1 }
111+
112+
// Gentle bounce
113+
{ type: 'spring', damping: 12, stiffness: 120, mass: 1 }
114+
115+
// Bouncy
116+
{ type: 'spring', damping: 8, stiffness: 200, mass: 1 }
117+
118+
// Slow and heavy
119+
{ type: 'spring', damping: 20, stiffness: 60, mass: 2 }
120+
```
121+
122+
### Animatable Properties
123+
124+
All properties are set in the `animate` prop as flat values (no transform array).
125+
126+
```tsx
127+
<EaseView
128+
animate={{
129+
opacity: 1, // 0 to 1
130+
translateX: 0, // pixels
131+
translateY: 0, // pixels
132+
scale: 1, // 1 = normal size
133+
rotate: 0, // degrees
134+
}}
135+
/>
136+
```
137+
138+
You can animate any combination of properties simultaneously. All properties share the same transition config.
139+
140+
### Enter Animations
141+
142+
Use `initialAnimate` to set starting values. On mount, the view starts at `initialAnimate` values and animates to `animate` values.
143+
144+
```tsx
145+
// Fade in and slide up on mount
146+
<EaseView
147+
initialAnimate={{ opacity: 0, translateY: 20 }}
148+
animate={{ opacity: 1, translateY: 0 }}
149+
transition={{ type: 'spring', damping: 15, stiffness: 120, mass: 1 }}
150+
/>
151+
```
152+
153+
Without `initialAnimate`, the view renders at the `animate` values immediately with no animation on mount.
154+
155+
### Interruption
156+
157+
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.
158+
159+
```tsx
160+
// Rapidly toggling this is fine — each toggle smoothly
161+
// redirects the animation from its current position
162+
<EaseView
163+
animate={{ translateX: isLeft ? 0 : 200 }}
164+
transition={{ type: 'spring', damping: 15, stiffness: 120, mass: 1 }}
165+
/>
10166
```
11167

168+
### Style Handling
12169

13-
## Usage
170+
Use `animate` for animated properties and `style` for everything else. If you accidentally put `opacity` or `transform` in `style`, they will be ignored and you'll get a dev warning.
14171

172+
```tsx
173+
// ✅ Correct
174+
<EaseView
175+
animate={{ opacity: 1, translateY: 0 }}
176+
style={{ backgroundColor: 'white', borderRadius: 12, padding: 16 }}
177+
/>
15178

16-
```js
17-
import { EaseView } from "react-native-ease";
179+
// ⚠️ opacity in style will be ignored with a warning
180+
<EaseView
181+
animate={{ translateY: 0 }}
182+
style={{ opacity: 0.5, backgroundColor: 'white' }}
183+
/>
184+
```
185+
186+
## API Reference
187+
188+
### `<EaseView>`
189+
190+
A `View` that animates property changes using native platform APIs.
191+
192+
| Prop | Type | Description |
193+
|---|---|---|
194+
| `animate` | `AnimateProps` | Target values for animated properties |
195+
| `initialAnimate` | `AnimateProps` | Starting values for enter animations (animates to `animate` on mount) |
196+
| `transition` | `Transition` | Animation configuration (timing or spring) |
197+
| `style` | `ViewStyle` | Non-animated styles (layout, colors, borders, etc.) |
198+
| `children` | `ReactNode` | Child elements |
199+
| ...rest | `ViewProps` | All other standard View props |
200+
201+
### `AnimateProps`
202+
203+
| Property | Type | Default | Description |
204+
|---|---|---|---|
205+
| `opacity` | `number` | `1` | View opacity (0–1) |
206+
| `translateX` | `number` | `0` | Horizontal translation in pixels |
207+
| `translateY` | `number` | `0` | Vertical translation in pixels |
208+
| `scale` | `number` | `1` | Uniform scale factor |
209+
| `rotate` | `number` | `0` | Rotation in degrees |
210+
211+
Properties not specified in `animate` default to their identity values.
18212

19-
// ...
213+
### `TimingTransition`
20214

21-
<EaseView color="tomato" />
215+
```tsx
216+
{
217+
type: 'timing';
218+
duration?: number; // default: 300 (ms)
219+
easing?: 'linear' | 'easeIn' | 'easeOut' | 'easeInOut'; // default: 'easeInOut'
220+
}
22221
```
23222

223+
### `SpringTransition`
224+
225+
```tsx
226+
{
227+
type: 'spring';
228+
damping?: number; // default: 15
229+
stiffness?: number; // default: 120
230+
mass?: number; // default: 1
231+
}
232+
```
233+
234+
## How It Works
235+
236+
`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:
237+
238+
1. **Diffs** previous vs new values to find what changed
239+
2. **Reads** the current in-flight value (for smooth interruption)
240+
3. **Creates** a platform-native animation from the current value to the new target
241+
4. **Sets** the final value immediately on the model layer
242+
243+
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.
244+
245+
## Requirements
246+
247+
- React Native 0.76+ (new architecture / Fabric)
248+
- iOS 15.1+
249+
- Android minSdk 24+
24250

25251
## Contributing
26252

android/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,5 @@ android {
6464

6565
dependencies {
6666
implementation "com.facebook.react:react-android"
67+
implementation "androidx.dynamicanimation:dynamicanimation:1.0.0"
6768
}

0 commit comments

Comments
 (0)