Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
build
.docusaurus
.cache-loader
.env
.DS_Store
16 changes: 16 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# React Native Ease docs

Docusaurus website for `react-native-ease`.

## Local development

```bash
yarn
yarn start
```

## Build

```bash
yarn build
```
97 changes: 97 additions & 0 deletions docs/docs/api-reference.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
---
id: api-reference
title: API reference
---

## `<EaseView>`

A `View` that animates property changes using native platform APIs.

| Prop | Type | Description |
| ------------------ | ---------------------------- | ----------- |
| `animate` | `AnimateProps` | Target values for animated properties |
| `initialAnimate` | `AnimateProps` | Starting values for enter animations |
| `transition` | `Transition` | Single config or per-property map |
| `onTransitionEnd` | `(event) => void` | Called when all animations complete with `{ finished: boolean }` |
| `transformOrigin` | `{ x?: number; y?: number }` | Pivot point for scale/rotation as 0–1 fractions |
| `useHardwareLayer` | `boolean` | Android only — rasterize to GPU texture during animations |
| `className` | `string` | NativeWind / Uniwind / Tailwind CSS class string |
| `style` | `ViewStyle` | Non-animated styles |
| `children` | `ReactNode` | Child elements |
| `...rest` | `ViewProps` | All other standard View props |

## `AnimateProps`

| Property | Type | Default | Description |
| ----------------- | ------------ | --------------- | ----------- |
| `opacity` | `number` | `1` | View opacity (0–1) |
| `translateX` | `number` | `0` | Horizontal translation in pixels |
| `translateY` | `number` | `0` | Vertical translation in pixels |
| `scale` | `number` | `1` | Uniform scale factor |
| `scaleX` | `number` | `1` | Horizontal scale factor |
| `scaleY` | `number` | `1` | Vertical scale factor |
| `rotate` | `number` | `0` | Z-axis rotation in degrees |
| `rotateX` | `number` | `0` | X-axis rotation in degrees |
| `rotateY` | `number` | `0` | Y-axis rotation in degrees |
| `borderRadius` | `number` | `0` | Border radius in pixels |
| `backgroundColor` | `ColorValue` | `'transparent'` | Background color |

## `TimingTransition`

```tsx
{
type: 'timing';
duration?: number;
easing?: EasingType;
delay?: number;
loop?: 'repeat' | 'reverse';
}
```

## `SpringTransition`

```tsx
{
type: 'spring';
damping?: number;
stiffness?: number;
mass?: number;
delay?: number;
}
```

## `NoneTransition`

```tsx
{
type: 'none';
}
```

Applies values instantly with no animation.

## `TransitionMap`

A per-property map that applies different transition configs to different property categories.

| Key | Properties |
| ----------------- | ---------- |
| `default` | Fallback for categories not explicitly listed |
| `transform` | translateX, translateY, scaleX, scaleY, rotate, rotateX, rotateY |
| `opacity` | opacity |
| `borderRadius` | borderRadius |
| `backgroundColor` | backgroundColor |

## Hardware layers (Android)

Setting `useHardwareLayer` rasterizes the view into a GPU texture for the duration of the animation.

```tsx
<EaseView animate={{ opacity: isVisible ? 1 : 0 }} useHardwareLayer />
```

**Trade-offs:**

- Faster rendering for opacity, scale, and rotation animations.
- Uses additional GPU memory for the off-screen texture.
- Overflowing children are clipped by the texture.
54 changes: 54 additions & 0 deletions docs/docs/benchmarks.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
id: benchmarks
title: Benchmarks
---

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.

## Android

UI thread time per frame: anim + layout + draw (ms). Lower is better.

![Android benchmark](https://github.com/user-attachments/assets/f0e5cf26-76be-4dd3-ae04-e17c6d13b49c)

<details>
<summary>Detailed numbers</summary>

| Views | Metric | Ease | Reanimated SV | Reanimated SV (FF) | Reanimated CSS | Reanimated CSS (FF) | RN Animated |
|-------|--------|------|---------------|---------------------|----------------|----------------------|-------------|
| 10 | Avg | 0.21 | 1.15 | 0.75 | 0.99 | 0.45 | 0.36 |
| 10 | P95 | 0.33 | 1.70 | 1.53 | 1.44 | 0.80 | 0.62 |
| 10 | P99 | 0.48 | 1.94 | 2.26 | 1.62 | 1.35 | 0.98 |
| 100 | Avg | 0.36 | 2.71 | 1.81 | 2.19 | 1.01 | 0.71 |
| 100 | P95 | 0.56 | 3.09 | 2.29 | 2.67 | 1.91 | 1.08 |
| 100 | P99 | 0.71 | 3.20 | 2.63 | 2.97 | 2.25 | 1.36 |
| 500 | Avg | 0.60 | 8.31 | 5.37 | 5.50 | 2.37 | 1.60 |
| 500 | P95 | 0.75 | 9.26 | 6.36 | 6.34 | 2.86 | 1.88 |
| 500 | P99 | 0.87 | 9.59 | 6.89 | 6.88 | 3.22 | 3.84 |

</details>

## iOS

Display link callback time per frame (ms). Lower is better.

![iOS benchmark](https://github.com/user-attachments/assets/c39a7a71-bf21-4276-b02f-b29983989832)

<details>
<summary>Detailed numbers</summary>

| Views | Metric | Ease | Reanimated SV | Reanimated SV (FF) | Reanimated CSS | Reanimated CSS (FF) | RN Animated |
|-------|--------|------|---------------|---------------------|----------------|----------------------|-------------|
| 10 | Avg | 0.01 | 1.33 | 1.08 | 1.06 | 0.63 | 0.83 |
| 10 | P95 | 0.02 | 1.67 | 1.59 | 1.34 | 1.01 | 1.18 |
| 10 | P99 | 0.03 | 1.90 | 1.68 | 1.50 | 1.08 | 1.31 |
| 100 | Avg | 0.01 | 3.72 | 3.33 | 2.71 | 2.48 | 3.32 |
| 100 | P95 | 0.01 | 5.21 | 4.50 | 3.83 | 3.39 | 4.28 |
| 100 | P99 | 0.02 | 5.68 | 4.75 | 4.91 | 3.79 | 4.55 |
| 500 | Avg | 0.01 | 6.84 | 6.54 | 4.16 | 3.70 | 4.91 |
| 500 | P95 | 0.01 | 7.69 | 7.32 | 4.59 | 4.22 | 5.66 |
| 500 | P99 | 0.02 | 8.10 | 7.45 | 4.71 | 4.33 | 5.89 |

</details>

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. On Android, ObjectAnimator runs on the UI thread but is significantly lighter than other approaches.
16 changes: 16 additions & 0 deletions docs/docs/contributing.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
id: contributing
title: Contributing
---

- [Development workflow](https://github.com/AppAndFlow/react-native-ease/blob/main/CONTRIBUTING.md#development-workflow)
- [Sending a pull request](https://github.com/AppAndFlow/react-native-ease/blob/main/CONTRIBUTING.md#sending-a-pull-request)
- [Code of conduct](https://github.com/AppAndFlow/react-native-ease/blob/main/CODE_OF_CONDUCT.md)

## Local docs development

```bash
cd docs
yarn
yarn start
```
115 changes: 115 additions & 0 deletions docs/docs/getting-started.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
---
id: getting-started
title: Getting started
slug: /
---

![react-native-ease hero](https://github.com/user-attachments/assets/8006ed51-d373-4c97-9e80-9937eb9a569e)

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

## Installation

```bash
npm install react-native-ease
# or
yarn add react-native-ease
```

## Example

```tsx
import { EaseView } from 'react-native-ease';

function FadeCard({ visible, children }) {
return (
<EaseView
animate={{ opacity: visible ? 1 : 0 }}
transition={{ type: 'timing', duration: 300 }}
style={styles.card}
>
{children}
</EaseView>
);
}
```

`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.

## Why Ease

### Goals

- **Fast** — Animations run entirely on native platform APIs (CAAnimation, ObjectAnimator/SpringAnimation). No JS animation loop, no worklets, no shared values.
- **Simple** — CSS-transition-like API. Set target values, get smooth animations. One component, a few props.
- **Lightweight** — Minimal native code, no C++ runtime, no custom animation engine. Just a thin declarative wrapper around what the OS already provides.
- **Interruptible** — Changing values mid-animation smoothly redirects to the new target. No jumps.

### Non-goals

- **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).
- **Layout animations** — Animating width/height/layout changes is not supported.
- **Shared element transitions** — Use Reanimated or React Navigation's shared element transitions.
- **Old architecture** — Fabric (new architecture) only.

## When to use this vs Reanimated

| Use case | Ease | Reanimated |
| -------------------------------------- | ---- | ---------- |
| Fade/slide/scale on state change | ✅ | |
| Enter/exit animations | ✅ | |
| Gesture-driven animations (pan, pinch) | | ✅ |
| Layout animations (width, height) | | ✅ |
| Complex interpolations & chaining | | ✅ |

## Styling integrations

### NativeWind support

If you're using [NativeWind](https://www.nativewind.dev/) (v4+), add this import once in your app's entry point (for example `_layout.tsx` or `App.tsx`):

```tsx
import 'react-native-ease/nativewind';
```

This registers `EaseView` with NativeWind's `cssInterop` so `className` is properly converted to styles:

```tsx
<EaseView
className="flex-1 bg-white rounded-2xl p-4"
animate={{ opacity: visible ? 1 : 0 }}
transition={{ type: 'timing', duration: 300 }}
>
{children}
</EaseView>
```

### Uniwind support

If you're using [Uniwind](https://docs.uniwind.dev/), first follow the [Uniwind quickstart](https://docs.uniwind.dev/quickstart) to install and configure Uniwind in your app.

Once Uniwind is set up, import `EaseView` from the Uniwind entry point:

```tsx
import { EaseView } from 'react-native-ease/uniwind';
```

```tsx
<EaseView
className="flex-1 bg-white rounded-2xl p-4"
animate={{ opacity: visible ? 1 : 0 }}
transition={{ type: 'timing', duration: 300 }}
>
{children}
</EaseView>
```

## Migration skill

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.

```bash
npx skills add appandflow/react-native-ease
```

Then invoke the skill in your agent (for example `/react-native-ease-refactor` in Claude Code).
19 changes: 19 additions & 0 deletions docs/docs/how-it-works.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
id: how-it-works
title: How it works
---

`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:

1. **Diffs** previous vs new values to find what changed
2. **Reads** the current in-flight value for smooth interruption
3. **Creates** a platform-native animation from the current value to the new target
4. **Sets** the final value immediately on the model layer

On iOS, this uses `CABasicAnimation` and `CASpringAnimation` on `CALayer` key paths. On Android, this uses `ObjectAnimator` and `SpringAnimation` on `View` properties. No JS thread involvement during the animation.

## Requirements

- React Native 0.76+ (new architecture / Fabric)
- iOS 15.1+
- Android minSdk 24+
Loading
Loading