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
245 changes: 92 additions & 153 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,113 +1,38 @@
# react-native-effects

https://github.com/user-attachments/assets/5141208a-655a-4de8-94fb-3e66351bf36f
WebGPU shader effects for React Native. Drop in a component, or write your own effect in WGSL. Rendering runs on a separate JS runtime, so a busy JS thread does not stall the animation.

> **Experimental** — APIs may change without notice. Relies on `react-native-worklets` bundle mode, which is not enabled by default yet.
<p align="center">
<img src="docs/media/demo.webp" alt="The example app: the effects catalog, Lava Lamp, Liquid Chrome, Thanos, Bubble Wrap, Ferrofluid, Polaroid and CRT Off" width="300">
</p>

WebGPU-powered effects running on **background thread** in React Native.
> **Experimental.** APIs may change without notice. The library relies on `react-native-worklets` bundle mode, which is off by default for now.

## Features

- **WebGPU rendering** via `react-native-webgpu`
- **Off-thread rendering** using `react-native-worklets` bundle mode — the GPU render loop runs on a separate JS runtime, keeping the main thread free
- **Drop-in components** — use like any React Native `View`
- **Customizable** — control colors, speed, intensity, and effect-specific parameters
- **Animated & static** modes for gradients
- **Build your own** — create custom effects with `ShaderView` and WGSL shaders

## Components

| Component | Description |
| ------------------ | ---------------------------------------------- |
| `Iridescence` | Mesmerizing iridescent animated effect |
| `LiquidChrome` | Fluid metallic surface |
| `Silk` | Smooth flowing silk fabric |
| `Campfire` | Fire with drifting sparks and smoke |
| `CalicoSwirl` | Warped noise pattern with flowing colors |
| `Aurora` | Northern lights with flowing curtains of light |
| `LinearGradient` | Smooth linear gradients (static & animated) |
| `CircularGradient` | Customizable circular/radial gradients |

## ShaderView

`ShaderView` is the core building block that powers every effect in this library. It takes a WGSL fragment shader and renders it on a WebGPU canvas, handling the render loop, uniform buffer, and React Native view integration for you.
## Quick look

```tsx
import { ShaderView } from 'react-native-effects';

<ShaderView
fragmentShader={myShader}
colors={['#ff0000', '#0000ff']}
params={[1.0, 0.5]}
speed={1.0}
style={{ width: '100%', height: 300 }}
/>;
```

| Prop | Type | Default | Description |
| ---------------------- | ---------------------- | ------- | ------------------------------------------------------------------------------------------------------- |
| `fragmentShader` | `string` | — | WGSL fragment shader source |
| `colors` | `ColorInput[]` | `[]` | Up to 2 colors mapped to `u.color0` and `u.color1` |
| `params` | `number[]` | `[]` | Up to 8 floats mapped to `u.params0.xyzw` and `u.params1.xyzw` |
| `speed` | `number` | `1.0` | Animation speed multiplier |
| `isStatic` | `boolean` | `false` | Render once then stop the animation loop |
| `transparent` | `boolean` | `false` | Clear the canvas to alpha `0` for an overlay-friendly transparent background |
| `paramsSynchronizable` | `ParamsSynchronizable` | — | Live 4-float input written into the dedicated `u.live` slot every frame (touch/scroll/audio). See below |

All built-in effects (Silk, Aurora, Campfire, etc.) are thin wrappers around `ShaderView`. You can use it directly to create your own custom effects — see the [Custom Effects Guide](CUSTOM_EFFECTS.md) for a full walkthrough and a ready-to-use AI prompt.

### Live input with `paramsSynchronizable`

Static `params` are great for values that change on the JS thread occasionally, but the render loop runs off-thread — so feeding it fast, per-frame input (a finger drag, scroll progress, audio level) through React props would be laggy. `paramsSynchronizable` is the bridge: a 4-float [Synchronizable](https://docs.swmansion.com/react-native-worklets/docs/synchronization/synchronizable) that the off-thread render loop reads every frame and writes into its own dedicated `u.live` slot — so it never collides with the static `params` (you keep all 8 _and_ get a live channel).
import { StyleSheet } from 'react-native';
import { Aurora } from 'react-native-effects';

Create one with the `useParamsSynchronizable` hook and update it from your gesture or scroll handlers:

```tsx
import { ShaderView, useParamsSynchronizable } from 'react-native-effects';

function TouchReactive() {
// initial resting value, read once: (x, y, active, extra)
const { paramsSynchronizable, setParamsSynchronizable } =
useParamsSynchronizable([0.5, 0.5, 0, 0]);

return (
<ShaderView
fragmentShader={myShader}
paramsSynchronizable={paramsSynchronizable}
style={{ width: '100%', height: 300 }}
onTouchMove={(e) => {
const { locationX, locationY } = e.nativeEvent;
setParamsSynchronizable(locationX, locationY, 1, 0);
}}
/>
);
export function Background() {
return <Aurora style={StyleSheet.absoluteFill} color="#4ade80" layers={3} />;
}
```

Inside the shader, declare the `live` field on the `Uniforms` struct (right after `params1`) and read the live values from `u.live`:

```wgsl
let pointer = u.live.xy; // (x, y) you wrote
let active = u.live.z; // 1 while touching, 0 otherwise
```

`setParamsSynchronizable(x, y, active, extra)` runs on the JS thread; the four floats are by convention `(x, y, active, extra)` for pointer input or `(progress, …)` for scroll-driven effects, but the meaning is entirely up to your shader. For a ready-made pan-gesture variant, use [`ShaderViewWithPanGesture`](src/components/ShaderViewWithPanGesture/index.tsx).
Effects accept standard `View` props, so they size and layer like any other view.

## Installation

```sh
npm install react-native-effects
npm install react-native-webgpu react-native-worklets react-native-gesture-handler
```

### Peer dependencies
Then complete the two steps below. The example app runs on React Native 0.88, Expo 58, `react-native-webgpu` 0.17 and `react-native-worklets` 0.13.

```sh
npm install react-native-webgpu react-native-worklets react-native-reanimated react-native-gesture-handler
```
### Android API level

### Android requirements

`react-native-webgpu` uses `AHardwareBuffer` APIs that require **Android API 26+**, so your app must set `minSdkVersion` to at least `26` (the default in many templates is `24`). In an Expo project, use [`expo-build-properties`](https://docs.expo.dev/versions/latest/sdk/build-properties/):
`react-native-webgpu` uses `AHardwareBuffer` APIs that require **Android API 26+**, and many templates default to 24. In a bare project, set `minSdkVersion = 26` in `android/build.gradle`. In Expo, use [`expo-build-properties`](https://docs.expo.dev/versions/latest/sdk/build-properties/):

```json
{
Expand All @@ -119,24 +44,9 @@ npm install react-native-webgpu react-native-worklets react-native-reanimated re
}
```

In a bare React Native project, set `minSdkVersion = 26` in `android/build.gradle`.

### Bundle mode setup
### Bundle mode

This library relies on `react-native-worklets` [Bundle Mode](https://docs.swmansion.com/react-native-worklets/docs/bundleMode). You need to configure Metro, Babel, and package.json in your app.

**package.json** (add at root level)

```json
{
"worklets": {
"staticFeatureFlags": {
"BUNDLE_MODE_ENABLED": true,
"FETCH_PREVIEW_ENABLED": true
}
}
}
```
The library needs `react-native-worklets` [Bundle Mode](https://docs.swmansion.com/react-native-worklets/docs/bundleMode). Enable it in Babel and Metro:

<details>
<summary><strong>Bare React Native</strong></summary>
Expand All @@ -155,7 +65,7 @@ module.exports = {
**metro.config.js**

```js
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const { getDefaultConfig } = require('@react-native/metro-config');
const {
getBundleModeMetroConfig,
} = require('react-native-worklets/bundleMode');
Expand All @@ -181,7 +91,7 @@ module.exports = config;
<details>
<summary><strong>Expo</strong></summary>

Install additional dev dependencies:
Install the additional dev dependencies:

```sh
npm install -D babel-preset-expo @react-native/metro-config
Expand Down Expand Up @@ -229,78 +139,107 @@ config.transformer = {
module.exports = config;
```

</details>

## Usage
Release builds on Expo also need the `require.getModules` polyfill: copy [`metro-get-modules-polyfill.js`](example/metro-get-modules-polyfill.js) and register it the way [`example/metro.config.js`](example/metro.config.js) does.

```tsx
import { Iridescence, LiquidChrome, Aurora } from 'react-native-effects';

// Full-screen animated background
<Iridescence style={StyleSheet.absoluteFillObject} />

// Metallic effect with custom speed
<LiquidChrome style={{ width: '100%', height: 300 }} speed={1.5} />

// Aurora borealis with custom parameters
<Aurora
style={StyleSheet.absoluteFillObject}
color="#4ade80"
speed={1.0}
intensity={1.0}
layers={3}
waviness={1.0}
/>
```
</details>

### Common Props
## Effects

All shader components accept standard `View` props plus:
The package exports eight ready-made components. Colors are `ColorInput` values: color strings or numbers.

| Prop | Type | Default | Description |
| ------- | ------------------ | ------- | -------------------------- |
| `color` | `string \| number` | varies | Base tint color |
| `speed` | `number` | `1.0` | Animation speed multiplier |
### Animated backgrounds

### Aurora
All of these take `color` and `speed` (a multiplier, default `1.0`), plus:

| Prop | Type | Default | Description |
| ----------- | -------- | ------- | ------------------------------ |
| `intensity` | `number` | `1.0` | Brightness of the aurora bands |
| `layers` | `number` | `3` | Number of curtain layers (1-5) |
| `waviness` | `number` | `1.0` | Turbulence of the curtains |
| Component | Looks like | Extra props |
| -------------- | ----------------------------------- | ---------------------------------------------- |
| `Aurora` | Northern lights | `intensity`, `layers` (1-5), `waviness` |
| `CalicoSwirl` | Warped noise with flowing colors | `intensity` |
| `Campfire` | Fire with drifting sparks and smoke | `sparkSize`, `fireIntensity`, `smokeIntensity` |
| `Iridescence` | Soap-film color | none |
| `LiquidChrome` | Fluid metallic surface | `amplitude`, `frequencyX`, `frequencyY` |
| `Silk` | Smooth flowing fabric | `scale`, `rotation`, `noiseIntensity` |

### Gradients

```tsx
import { LinearGradient, CircularGradient } from 'react-native-effects';
import { CircularGradient, LinearGradient } from 'react-native-effects';

<LinearGradient
style={{ width: '100%', height: 200 }}
colors={['#ff0000', '#0000ff']}
startColor="#ff0000"
endColor="#0000ff"
angle={45}
/>

<CircularGradient
style={{ width: 200, height: 200 }}
colors={['#ff0000', '#0000ff']}
centerColor="#ff0000"
edgeColor="#0000ff"
/>
```

## Create Your Own Effect
| Component | Required props | Optional props |
| ------------------ | --------------------------------- | --------------------------------------------------- |
| `LinearGradient` | `startColor`, `endColor`, `angle` | `speed` in degrees per second, `0` is static |
| `CircularGradient` | `centerColor` | `edgeColor`, `centerX`, `centerY`, `sizeX`, `sizeY` |

## Build your own

Every effect above is a thin wrapper around `ShaderView`, which takes a WGSL fragment shader and handles the WebGPU canvas, render loop and uniform buffer.

```tsx
import { ShaderView } from 'react-native-effects';

<ShaderView
fragmentShader={myShader}
colors={['#ff0000', '#0000ff']}
params={[1.0, 0.5]}
speed={1.0}
style={{ width: '100%', height: 300 }}
/>;
```

| Prop | Type | Default | Description |
| ---------------------- | ---------------------- | ------- | -------------------------------------------------------------------- |
| `fragmentShader` | `string` | none | WGSL fragment shader source |
| `colors` | `ColorInput[]` | `[]` | Up to 2 colors, mapped to `u.color0` and `u.color1` |
| `params` | `number[]` | `[]` | Up to 8 floats, mapped to `u.params0.xyzw` and `u.params1.xyzw` |
| `speed` | `number` | `1.0` | Animation speed multiplier |
| `isStatic` | `boolean` | `false` | Render once, then stop the animation loop |
| `transparent` | `boolean` | `false` | Clear to alpha `0`, for overlays |
| `texture` | `ImageSourcePropType` | none | Image the shader can sample, for effects on top of real photos |
| `paramsSynchronizable` | `ParamsSynchronizable` | none | Live floats written to `u.live` every frame, independent of `params` |

### Live input

React props are too slow for per-frame input such as a finger drag or an audio level. `ShaderViewWithPanGesture` feeds touch straight to the render loop: `u.live.xy` is the pointer in UV space and `u.live.z` is `1` while touching.

```tsx
import { ShaderViewWithPanGesture } from 'react-native-effects';

<ShaderViewWithPanGesture
fragmentShader={myShader}
initialParamsSynchronizable={[0.5, 0.5, 0, 0]}
style={{ width: '100%', height: 300 }}
/>;
```

For scroll, audio or any other source, create your own channel with `useParamsSynchronizable` and pass it as `paramsSynchronizable`.

The [Custom Effects Guide](CUSTOM_EFFECTS.md) covers the uniform layout, the shader contract and live input in full, and includes a prompt you can paste into an AI assistant to generate an effect.

Want to build a custom shader effect? Check out the [Custom Effects Guide](CUSTOM_EFFECTS.md) — it includes everything you need to know about the `ShaderView` API, the uniform layout, and the WGSL shader contract. It also has a ready-to-use AI prompt you can paste into ChatGPT or Claude to generate a complete custom effect.
## Example app

## Running the example app
The clip above is the [example app](example), a catalog of 45 effects built with `ShaderView`. Most of them, including everything in the clip except Liquid Chrome, are demos in `example/src`. The package does not export them, so treat them as source to copy from. Many react to tilt, touch, sound or the magnetometer.

```sh
yarn install
yarn prebuild # generates native iOS/Android projects
yarn ios # run on iOS simulator
yarn android # run on Android emulator
yarn example prebuild
yarn example ios # or: yarn example android
```

To start fresh after config changes, use `yarn prebuild:clean` instead of `yarn prebuild`.
Use `yarn example prebuild:clean` to start fresh after config changes. The app's photo and software credits are in [docs/credits.md](docs/credits.md).

## Contributing

Expand Down
Binary file added docs/media/demo.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading