react-voodoo 2.5.16 → 2.6.1
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/README.md +274 -0
- package/babel.config.js +6 -0
- package/dist/react-voodoo.js +1 -1
- package/dist/react-voodoo.js.map +1 -1
- package/jest.config.js +16 -0
- package/package.json +10 -4
- package/readme.md +152 -89
package/README.md
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# react-voodoo
|
|
2
|
+
|
|
3
|
+
Additive, swipeable, SSR-ready animation engine for React.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/react-voodoo)
|
|
6
|
+
[](#license)
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
react-voodoo drives animations by writing CSS **directly to the DOM**, bypassing React's render loop entirely. Scroll positions, drag gestures, and programmatic tweens all feed into the same additive accumulator, so multiple concurrent animations compose without conflict.
|
|
11
|
+
|
|
12
|
+
The engine is built on [tween-axis](../tween-axis/README.md) and uses its WebAssembly backend for hot-path property accumulation with zero JS-boundary crossings per frame.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install react-voodoo
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Requires React 16, 17, or 18.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Core concepts
|
|
27
|
+
|
|
28
|
+
### Delta-based accumulation
|
|
29
|
+
|
|
30
|
+
Every animation operates on **deltas** — the change from the previous timeline position — rather than absolute target values. On each scroll or animation frame, `goTo(newPos, tweenRefMaps)` emits deltas that are added into a numeric accumulator (`tweenRefMaps`). A separate mux pass then converts those numbers into CSS strings and writes them to `node.style` directly.
|
|
31
|
+
|
|
32
|
+
This architecture enables:
|
|
33
|
+
- Multiple axes and one-shot animations to animate the same property simultaneously (additive composition).
|
|
34
|
+
- Direct DOM writes that skip React's reconciler entirely.
|
|
35
|
+
- Predictive inertia and physics-based scrolling with no render overhead.
|
|
36
|
+
|
|
37
|
+
### CSS demux / mux
|
|
38
|
+
|
|
39
|
+
Tween descriptors specify CSS properties in their `apply` object. Before registering with `TweenAxis`, the `deMuxLine` pass converts each CSS value into a set of flat numeric keys (e.g. `transform_0_translateX_9`). On every frame the inverse `muxToCss` pass reconstructs the CSS strings (including `calc()` for multi-unit values) and writes them to the DOM.
|
|
40
|
+
|
|
41
|
+
### WASM acceleration
|
|
42
|
+
|
|
43
|
+
For tween descriptors without event callbacks and with a standard easing function, `CssTweenAxis` (the internal axis class) automatically switches the process to `PROC_WASM` mode. The WebAssembly engine accumulates the property deltas directly inside WASM — no JS function call per property per frame. Results are flushed back into `tweenRefMaps` in a tight loop after `goTo()` returns.
|
|
44
|
+
|
|
45
|
+
Descriptors that use custom easing functions or event callbacks (`entering`/`moving`/`leaving`) are transparently handled by the JS path (`PROC_RESULT`), so there is no behavioural change — WASM acceleration applies automatically where possible.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Components
|
|
50
|
+
|
|
51
|
+
### `<Component>` / `asTweener`
|
|
52
|
+
|
|
53
|
+
The root animation engine. Wrap your animated tree with it to create a `Tweener` context.
|
|
54
|
+
|
|
55
|
+
```jsx
|
|
56
|
+
import { Component } from "react-voodoo";
|
|
57
|
+
// or with the HOC:
|
|
58
|
+
import { tweener as asTweener } from "react-voodoo";
|
|
59
|
+
|
|
60
|
+
@asTweener
|
|
61
|
+
class MyScene extends React.Component { ... }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### `<Node>`
|
|
65
|
+
|
|
66
|
+
An animatable React element. Registers its CSS tween descriptors with the parent `Tweener`.
|
|
67
|
+
|
|
68
|
+
```jsx
|
|
69
|
+
import { Node } from "react-voodoo";
|
|
70
|
+
|
|
71
|
+
<Node
|
|
72
|
+
id="card"
|
|
73
|
+
style={{ position: "absolute" }}
|
|
74
|
+
tweenLines={[
|
|
75
|
+
{
|
|
76
|
+
from: 0, duration: 500,
|
|
77
|
+
apply: { opacity: 1, transform: [{ translateY: "100px" }] },
|
|
78
|
+
easeFn: "easeQuadInOut",
|
|
79
|
+
}
|
|
80
|
+
]}
|
|
81
|
+
/>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### `<Axis>`
|
|
85
|
+
|
|
86
|
+
A scrollable animation timeline. Attach it to a scroll axis by `id`.
|
|
87
|
+
|
|
88
|
+
```jsx
|
|
89
|
+
import { Axis } from "react-voodoo";
|
|
90
|
+
|
|
91
|
+
<Axis
|
|
92
|
+
id="scrollY"
|
|
93
|
+
size={1000}
|
|
94
|
+
defaultPosition={0}
|
|
95
|
+
inertia={{ snapToBounds: true, wayPoints: [{ at: 0 }, { at: 500 }] }}
|
|
96
|
+
/>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### `<Draggable>`
|
|
100
|
+
|
|
101
|
+
Maps touch/mouse drag gestures to an axis position.
|
|
102
|
+
|
|
103
|
+
```jsx
|
|
104
|
+
import { Draggable } from "react-voodoo";
|
|
105
|
+
|
|
106
|
+
<Draggable axisId="scrollY" />
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Hooks
|
|
112
|
+
|
|
113
|
+
### `useVoodoo(opts?)`
|
|
114
|
+
|
|
115
|
+
Primary hook. Creates or inherits a `Tweener` instance and returns `[tweener, ViewBox]`.
|
|
116
|
+
|
|
117
|
+
```jsx
|
|
118
|
+
import { useVoodoo } from "react-voodoo";
|
|
119
|
+
|
|
120
|
+
function Scene() {
|
|
121
|
+
const [tweener, ViewBox] = useVoodoo();
|
|
122
|
+
return <ViewBox>{...}</ViewBox>;
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### `useTweener()`
|
|
127
|
+
|
|
128
|
+
Read-only access to the nearest parent `Tweener`.
|
|
129
|
+
|
|
130
|
+
```jsx
|
|
131
|
+
import { useTweener } from "react-voodoo";
|
|
132
|
+
|
|
133
|
+
function Inner() {
|
|
134
|
+
const tweener = useTweener();
|
|
135
|
+
// tweener.scrollTo("scrollY", 500, 300);
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Tween descriptor
|
|
142
|
+
|
|
143
|
+
```js
|
|
144
|
+
{
|
|
145
|
+
from: 0, // Start position on the axis (omit for sequential chaining)
|
|
146
|
+
duration: 500, // Length of this segment
|
|
147
|
+
target: "nodeId", // ID of the Node to animate
|
|
148
|
+
apply: {
|
|
149
|
+
opacity: 1,
|
|
150
|
+
transform: [{ translateX: "100px", translateY: "-50%" }],
|
|
151
|
+
// Multi-unit values use CSS calc():
|
|
152
|
+
width: ["50%", "10vw", "-50px"],
|
|
153
|
+
},
|
|
154
|
+
easeFn: "easeQuadInOut", // String key into TweenAxis.EasingFunctions (d3-ease)
|
|
155
|
+
entering: (delta) => {}, // Called when the process activates
|
|
156
|
+
moving: (pos, prev, delta) => {},
|
|
157
|
+
leaving: (delta) => {},
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Supported CSS properties
|
|
162
|
+
|
|
163
|
+
All standard animatable CSS properties are supported. Complex properties have dedicated demuxers:
|
|
164
|
+
|
|
165
|
+
| Property | Notes |
|
|
166
|
+
|----------|-------|
|
|
167
|
+
| `transform` | Array of layer objects; each key is a transform function (`translateX`, `rotate`, etc.) |
|
|
168
|
+
| `boxShadow` | Full multi-shadow support |
|
|
169
|
+
| `filter` | CSS filter functions |
|
|
170
|
+
| `backgroundColor` | RGBA component interpolation |
|
|
171
|
+
| `textShadow` | Multi-shadow support |
|
|
172
|
+
| `opacity`, `zIndex` | Numeric |
|
|
173
|
+
| `width`, `height`, `top`, `left`, `right`, `bottom`, margins, paddings, borders | Length with unit |
|
|
174
|
+
| SVG attributes (`cx`, `cy`, `r`, `x`, `y`, …) | Applied via `setAttribute` |
|
|
175
|
+
|
|
176
|
+
### Multi-unit values
|
|
177
|
+
|
|
178
|
+
Arrays of CSS values are resolved with `calc()`:
|
|
179
|
+
|
|
180
|
+
```js
|
|
181
|
+
width: ["50%", "10vw", "-50px"]
|
|
182
|
+
// → calc(50% + 10vw - 50px)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Axis configuration
|
|
188
|
+
|
|
189
|
+
```js
|
|
190
|
+
{
|
|
191
|
+
id: "scrollY",
|
|
192
|
+
defaultPosition: 0,
|
|
193
|
+
size: 1000, // Timeline length (matches tween descriptor positions)
|
|
194
|
+
bounds: { min: 0, max: 1000 },
|
|
195
|
+
inertia: {
|
|
196
|
+
snapToBounds: true,
|
|
197
|
+
wayPoints: [{ at: 0 }, { at: 500 }, { at: 1000 }],
|
|
198
|
+
willSnap: (index, wp) => {},
|
|
199
|
+
onSnap: (index, wp) => {},
|
|
200
|
+
},
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Tweener API (imperative)
|
|
207
|
+
|
|
208
|
+
The `Tweener` instance is accessible via `useVoodoo`, `useTweener`, or `tweenerOptions.ref`.
|
|
209
|
+
|
|
210
|
+
### `tweener.scrollTo(axisId, position, durationMs?, easing?, noEvents?, tick?, cb?)`
|
|
211
|
+
|
|
212
|
+
Animate an axis to a position over time.
|
|
213
|
+
|
|
214
|
+
### `tweener.addScrollableAnim(anim, axisId?)`
|
|
215
|
+
|
|
216
|
+
Register a tween line (array of descriptors) on a scroll axis at runtime. Returns the `CssTweenAxis` instance.
|
|
217
|
+
|
|
218
|
+
### `tweener.rmScrollableAnim(sl, axisId?)`
|
|
219
|
+
|
|
220
|
+
Remove a previously registered tween line.
|
|
221
|
+
|
|
222
|
+
### `tweener.pushAnim(descriptors, durationMs, cb?, keepResults?)`
|
|
223
|
+
|
|
224
|
+
Run a one-shot animation that plays to completion and then optionally releases its CSS values.
|
|
225
|
+
|
|
226
|
+
### `tweener.watchAxis(axisId, listener)`
|
|
227
|
+
|
|
228
|
+
Subscribe to scroll position changes on an axis. Returns an unsubscribe function.
|
|
229
|
+
|
|
230
|
+
### `tweener.getScrollPos(axisId)`
|
|
231
|
+
|
|
232
|
+
Return the current scroll position of an axis.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## WASM acceleration in CssTweenAxis
|
|
237
|
+
|
|
238
|
+
`CssTweenAxis` (the internal class used by every axis) automatically activates PROC_WASM for eligible tween descriptors:
|
|
239
|
+
|
|
240
|
+
**Eligible** (WASM path):
|
|
241
|
+
- No event callbacks (`entering` / `moving` / `leaving`).
|
|
242
|
+
- `easeFn` is `undefined`/`null` (linear) **or** one of the 10 mapped d3-ease functions.
|
|
243
|
+
|
|
244
|
+
**Not eligible** (JS path, no change in behaviour):
|
|
245
|
+
- Descriptors with event callbacks.
|
|
246
|
+
- Descriptors using custom easing functions not in the built-in set.
|
|
247
|
+
|
|
248
|
+
The 10 d3-ease functions that map to WASM built-ins:
|
|
249
|
+
|
|
250
|
+
| d3-ease name | WASM easing |
|
|
251
|
+
|--------------|-------------|
|
|
252
|
+
| *(none / linear)* | `EASE_LINEAR` |
|
|
253
|
+
| `easeQuadIn` / `easeQuadOut` / `easeQuadInOut` | `EASE_IN/OUT/INOUT_QUAD` |
|
|
254
|
+
| `easeCubicIn` / `easeCubicOut` / `easeCubicInOut` | `EASE_IN/OUT/INOUT_CUBIC` |
|
|
255
|
+
| `easeExpIn` / `easeExpOut` / `easeExpInOut` | `EASE_IN/OUT/INOUT_EXPO` |
|
|
256
|
+
|
|
257
|
+
No configuration is required — the selection happens automatically during axis construction.
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Building
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
cd react-voodoo
|
|
265
|
+
npm run build # production build via lpack
|
|
266
|
+
npm run devLib # watch mode
|
|
267
|
+
npm run setupLayers # initialise lpack layer config
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## License
|
|
273
|
+
|
|
274
|
+
Dual-licensed: **CC-BY-ND-4.0** or **AGPL-3.0-only**. See [LICENSE](./LICENSE) for details.
|