react-voodoo 2.6.1 → 2.6.2
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 +312 -216
- package/dist/react-voodoo.js +1 -1
- package/dist/react-voodoo.js.map +1 -1
- package/package.json +1 -1
- package/readme.md +0 -340
package/README.md
CHANGED
|
@@ -1,274 +1,370 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">react-voodoo</h1>
|
|
2
|
+
<p align="center"><b>Additive · Swipeable · SSR-ready · Physics-based</b><br/>A delta-driven tween composition engine for React</p>
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
<p align="center"><img width="192" src="https://github.com/react-voodoo/react-voodoo/raw/master/doc/assets/logo-v0.png?sanitize=true" /></p>
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
<p align="center">
|
|
7
|
+
<a href="https://www.npmjs.com/package/react-voodoo"><img src="https://img.shields.io/npm/v/react-voodoo.svg" alt="npm version" /></a>
|
|
8
|
+
<a href="https://travis-ci.org/react-voodoo/react-voodoo"><img src="https://travis-ci.org/react-voodoo/react-voodoo.svg?branch=master" alt="Build Status" /></a>
|
|
9
|
+
<img src="https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat" />
|
|
10
|
+
<br/>
|
|
11
|
+
<a href="http://creativecommons.org/licenses/by-nd/4.0"><img src="https://img.shields.io/badge/License-CC%20BY--ND%204.0-lightgrey.svg" alt="License: CC BY-ND 4.0" /></a>
|
|
12
|
+
<a href="http://www.gnu.org/licenses/agpl-3.0"><img src="https://img.shields.io/badge/License-AGPL%20v3-blue.svg" alt="License: AGPL v3" /></a>
|
|
13
|
+
</p>
|
|
7
14
|
|
|
8
|
-
|
|
15
|
+
<p align="center">
|
|
16
|
+
<a href="doc/readme.md"><b>Full documentation</b></a> ·
|
|
17
|
+
<a href="https://react-voodoo.github.io/react-voodoo-samples/"><b>Live demos & CodeSandbox</b></a> ·
|
|
18
|
+
<a href="https://github.com/react-voodoo/react-voodoo-samples"><b>Sample sources</b></a>
|
|
19
|
+
</p>
|
|
9
20
|
|
|
10
|
-
react-voodoo
|
|
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.
|
|
21
|
+
<p align="center"><img src="https://github.com/react-voodoo/react-voodoo/raw/master/doc/assets/demo.gif?sanitize=true" /></p>
|
|
13
22
|
|
|
14
23
|
---
|
|
15
24
|
|
|
16
|
-
##
|
|
25
|
+
## Why react-voodoo?
|
|
17
26
|
|
|
18
|
-
|
|
19
|
-
npm install react-voodoo
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
Requires React 16, 17, or 18.
|
|
23
|
-
|
|
24
|
-
---
|
|
27
|
+
Most animation libraries output **absolute values** — they own a CSS property and write a number to it each frame. That works fine for isolated transitions, but breaks down the moment you need to combine sources: a scroll position driving `translateY`, a drag gesture adding to the same `translateY`, and a parallax offset stacking on top. The libraries fight each other and you end up writing glue code.
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
React-voodoo takes a different approach: its engine computes **deltas** — the *change* from the previous frame — and accumulates them additively across any number of axes. Multiple animations on the same property simply add together. No ownership, no conflicts.
|
|
27
30
|
|
|
28
|
-
|
|
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
|
|
31
|
+
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.
|
|
42
32
|
|
|
43
|
-
|
|
33
|
+
This unlocks a set of features that are unique to the delta model:
|
|
44
34
|
|
|
45
|
-
|
|
35
|
+
| Feature | How |
|
|
36
|
+
|---|---|
|
|
37
|
+
| **Additive multi-axis composition** | Each axis contributes a delta; they stack without coordination code. |
|
|
38
|
+
| **Swipeable / draggable animations** | Drag gestures are mapped directly to axis positions with realistic momentum. |
|
|
39
|
+
| **Predictive inertia** | The engine computes the final snap target *at the moment of release*, before the animation plays out — useful for preloading the next slide. |
|
|
40
|
+
| **SSR with correct initial styles** | Axes have a `defaultPosition`; styles are computed server-side and rendered inline — no flash on first paint. |
|
|
41
|
+
| **DOM writes bypass React** | Style updates go straight to `node.style` via direct DOM writes, never triggering a re-render. |
|
|
42
|
+
| **Multi-unit CSS via `calc()`** | Mix `%`, `px`, `vw`, `bw`/`bh` (box-relative units) in a single value — compiled to `calc()` automatically. |
|
|
46
43
|
|
|
47
44
|
---
|
|
48
45
|
|
|
49
|
-
##
|
|
50
|
-
|
|
51
|
-
###
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
## Comparison
|
|
47
|
+
|
|
48
|
+
### Feature matrix
|
|
49
|
+
|
|
50
|
+
| | **react-voodoo** | Framer Motion v12 | GSAP + ScrollTrigger | react-spring v10 | Motion v5 | anime.js v4 |
|
|
51
|
+
|---|:---:|:---:|:---:|:---:|:---:|:---:|
|
|
52
|
+
| Scroll-linked animation | ✅ | ✅ `useScroll` | ✅ | ⚠️ manual | ✅ | ✅ `ScrollObserver` |
|
|
53
|
+
| Drag-linked animation | ✅ native | ✅ `drag` | ⚠️ manual | ✅ `@use-gesture` | ✅ gestures | ✅ `Draggable` |
|
|
54
|
+
| **Additive multi-axis composition** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
55
|
+
| Physics / momentum inertia | ✅ predictive | ✅ spring | ❌ | ✅ spring | ⚠️ limited | ✅ spring |
|
|
56
|
+
| **Predictive snap target** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
57
|
+
| **SSR — correct initial styles** | ✅ | ⚠️ flash | ⚠️ flash | ⚠️ flash | ⚠️ flash | ❌ |
|
|
58
|
+
| Bypasses React render loop | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
59
|
+
| Transform layer composition | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
60
|
+
| SVG geometry attributes | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ |
|
|
61
|
+
| Multitouch (drag multiple axes) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
62
|
+
| Bundle size (approx. gzip) | ~18 kB | ~32 kB | ~35 kB | ~25 kB | ~4 kB | ~10 kB |
|
|
63
|
+
| React dependency | ≥ 16 | ≥ 18 | none | ≥ 16 | none | none |
|
|
54
64
|
|
|
55
|
-
|
|
56
|
-
import { Component } from "react-voodoo";
|
|
57
|
-
// or with the HOC:
|
|
58
|
-
import { tweener as asTweener } from "react-voodoo";
|
|
65
|
+
---
|
|
59
66
|
|
|
60
|
-
|
|
61
|
-
class MyScene extends React.Component { ... }
|
|
62
|
-
```
|
|
67
|
+
## Performance
|
|
63
68
|
|
|
64
|
-
|
|
69
|
+
The delta model isn't just an architectural choice — the numbers back it up.
|
|
70
|
+
In the scenarios that define react-voodoo's use-case — **compositing multiple animation
|
|
71
|
+
sources on the same element** — the engine runs **3–7× faster than GSAP** and handles
|
|
72
|
+
far more properties per frame without degrading.
|
|
65
73
|
|
|
66
|
-
|
|
74
|
+
<p align="center">
|
|
75
|
+
<img src="./doc/assets/perf-chart.svg" alt="Performance comparison chart" width="960"/>
|
|
76
|
+
</p>
|
|
67
77
|
|
|
68
|
-
|
|
69
|
-
|
|
78
|
+
<details>
|
|
79
|
+
<summary><b>What each scenario measures</b></summary>
|
|
70
80
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
easeFn: "easeQuadInOut",
|
|
79
|
-
}
|
|
80
|
-
]}
|
|
81
|
-
/>
|
|
82
|
-
```
|
|
81
|
+
| Scenario | Description |
|
|
82
|
+
|---|---|
|
|
83
|
+
| **sequential · 5 props** | Frame-by-frame advance, 5 CSS properties — the baseline everyone tests |
|
|
84
|
+
| **property scale · 20 props** | Same advance with 20 properties — reveals engine scaling cost per property |
|
|
85
|
+
| **additive ×3** | Three independent animation axes all writing to the same 5 properties simultaneously. This is react-voodoo's **native model**. Competitors must run 3 separate timelines and manually sum results. |
|
|
86
|
+
| **spring layers** | Same 3 axes at different speeds (×1, ×0.7, ×0.3) — the signature scroll + drag + push composition pattern react-voodoo is built for |
|
|
87
|
+
| **long timeline · 20 segs** | A timeline with 20 sequential animation segments — models a full-page scroll sequence or complex keyframe chain |
|
|
83
88
|
|
|
84
|
-
|
|
89
|
+
</details>
|
|
85
90
|
|
|
86
|
-
|
|
91
|
+
<details>
|
|
92
|
+
<summary><b>Why voodoo wins at scale</b></summary>
|
|
87
93
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
<Axis
|
|
92
|
-
id="scrollY"
|
|
93
|
-
size={1000}
|
|
94
|
-
defaultPosition={0}
|
|
95
|
-
inertia={{ snapToBounds: true, wayPoints: [{ at: 0 }, { at: 500 }] }}
|
|
96
|
-
/>
|
|
97
|
-
```
|
|
94
|
+
- **Compiled-per-property processors** — at mount time each tween compiles a dedicated function (via `new Function`) that contains only the branches it needs. There is no per-property dispatch loop at runtime; property count scales at near O(1).
|
|
95
|
+
- **WASM state machine** — the marker-scan loop (the part that decides which tweens are active as the cursor moves) runs entirely in WebAssembly. Zero JS-boundary crossings per frame in the hot path.
|
|
96
|
+
- **Additive is free** — `goTo(pos, scope)` accumulates deltas into the same plain object regardless of how many axes call it. Competitors must maintain N separate target objects and merge them manually.
|
|
98
97
|
|
|
99
|
-
|
|
98
|
+
The one scenario where a stateless interpolator (Framer Motion / Popmotion) wins is
|
|
99
|
+
a **single small axis with easing**: pure math functions with no timeline state beat even
|
|
100
|
+
WASM for trivially short timelines. That overhead is the fair cost of additive composability —
|
|
101
|
+
and it disappears entirely once you have more than one axis compositing on the same element.
|
|
100
102
|
|
|
101
|
-
|
|
103
|
+
</details>
|
|
102
104
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
<Draggable axisId="scrollY" />
|
|
107
|
-
```
|
|
105
|
+
> Full benchmark source: [`perf-compare/bench.js`](../perf-compare/bench.js)
|
|
106
|
+
> Detailed analysis: [`doc/Alternatives libs perf comparaison.md`](doc/Alternatives%20libs%20perf%20comparaison.md)
|
|
108
107
|
|
|
109
108
|
---
|
|
110
109
|
|
|
111
|
-
##
|
|
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";
|
|
110
|
+
## Installation
|
|
119
111
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return <ViewBox>{...}</ViewBox>;
|
|
123
|
-
}
|
|
112
|
+
```bash
|
|
113
|
+
npm install react-voodoo
|
|
124
114
|
```
|
|
125
115
|
|
|
126
|
-
|
|
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
|
-
```
|
|
116
|
+
**Peer dependencies:** `react >= 16`, `react-dom >= 16`
|
|
138
117
|
|
|
139
118
|
---
|
|
140
119
|
|
|
141
|
-
##
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
120
|
+
## All-in-one example
|
|
121
|
+
|
|
122
|
+
Every major feature in a single component, with comments explaining each part.
|
|
123
|
+
|
|
124
|
+
```jsx harmony
|
|
125
|
+
import React from "react";
|
|
126
|
+
import Voodoo from "react-voodoo";
|
|
127
|
+
import {itemTweenAxis, tweenArrayWithTargets} from "./somewhere";
|
|
128
|
+
|
|
129
|
+
const styleSample = {
|
|
130
|
+
/**
|
|
131
|
+
* Voodoo.Node style property and the tween descriptors use classic CSS-in-JS declaration
|
|
132
|
+
* exept we can specify values using the "box" unit which is a [0-1] ratio of the parent ViewBox height / width
|
|
133
|
+
*/
|
|
134
|
+
|
|
135
|
+
height: "50%",
|
|
136
|
+
|
|
137
|
+
// the tweener deal with multiple units
|
|
138
|
+
// it will use css calc fn to add them if there's more than 1 unit used
|
|
139
|
+
width: ["50%", "10vw", "-50px", ".2box"],
|
|
140
|
+
|
|
141
|
+
// transform can use multiple "layers"
|
|
142
|
+
transform: [
|
|
143
|
+
{
|
|
144
|
+
// use rotate(X|Y|Z) & translate(X|Y|Z)
|
|
145
|
+
rotateX: "25deg"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
translateZ: "-.2box"
|
|
149
|
+
}
|
|
150
|
+
],
|
|
151
|
+
|
|
152
|
+
filter:
|
|
153
|
+
{
|
|
154
|
+
blur: "5px"
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
const axisSample = [// Examples of tween descriptors
|
|
158
|
+
{
|
|
159
|
+
target : "someTweenRefId", // target Voodoo.Node id ( optional if used as parameter on a Voodoo.Node as it will target it )
|
|
160
|
+
from : 0, // tween start position
|
|
161
|
+
duration: 100, // tween duration
|
|
162
|
+
easeFn : "easeCircleIn", // function or easing fn id from [d3-ease](https://github.com/d3/d3-ease)
|
|
163
|
+
|
|
164
|
+
apply: {// relative css values to be applied
|
|
165
|
+
// Same syntax as the styles
|
|
166
|
+
transform: [{}, {
|
|
167
|
+
translateZ: "-.2box"
|
|
168
|
+
}]
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
from : 40,
|
|
173
|
+
duration: 20,
|
|
174
|
+
|
|
175
|
+
// triggered when axis has scrolled in the Event period
|
|
176
|
+
// delta : a float value between [-1,1] is the update inside the Event period
|
|
177
|
+
entering: ( delta ) => false,
|
|
178
|
+
|
|
179
|
+
// triggered when axis has scrolled in the Event period
|
|
180
|
+
// newPos, precPos : float values between [0,1] position inside the Event period
|
|
181
|
+
// delta : a float value between [-1,1] is the update inside the Event period
|
|
182
|
+
moving: ( newPos, precPos, delta ) => false,
|
|
183
|
+
|
|
184
|
+
// triggered when axis has scrolled out the Event period
|
|
185
|
+
// delta : a float value between [-1,1] is the update inside the Event period
|
|
186
|
+
leaving: ( delta ) => false
|
|
187
|
+
}
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
const Sample = ( {} ) => {
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Voodoo tweener instanciation
|
|
194
|
+
*/
|
|
195
|
+
// Classic minimal method
|
|
196
|
+
const [tweener, ViewBox] = Voodoo.hook();
|
|
197
|
+
// get the first tweener in parents
|
|
198
|
+
const [parentTweener] = Voodoo.hook(true);
|
|
199
|
+
// Create a tweener with options
|
|
200
|
+
const [twenerWithNameAndOptions, ViewBox2] = Voodoo.hook(
|
|
201
|
+
{
|
|
202
|
+
// Give an id to this tweener so we can access it's axes in the childs components
|
|
203
|
+
name: "root",
|
|
204
|
+
// max click tm in ms before a click become a drag
|
|
205
|
+
maxClickTm: 200,
|
|
206
|
+
// max drag offset in px before a click become a drag
|
|
207
|
+
maxClickOffset: 100,
|
|
208
|
+
// lock to only 1 drag direction
|
|
209
|
+
dragDirectionLock: false,
|
|
210
|
+
// allow dragging with mouse
|
|
211
|
+
enableMouseDrag: false
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
// get a named parent tweener
|
|
215
|
+
const [nammedParentTweener] = Voodoo.hook("root")
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* once first render done, axes expose the following values & functions :
|
|
219
|
+
*/
|
|
220
|
+
// Theirs actual position in :
|
|
221
|
+
// tweener.axes.(axisId).scrollPos
|
|
222
|
+
|
|
223
|
+
// The "scrollTo" function allowing to manually move the axes positions :
|
|
224
|
+
// tweener.axes.(axisId).scrollTo(targetPos, duration, easeFn)
|
|
225
|
+
// tweener.scrollTo(targetPos, duration, axisId, easeFn)
|
|
226
|
+
|
|
227
|
+
// They can also be watched using the "watchAxis" function;
|
|
228
|
+
// When called, the returned function will disable the listener if executed :
|
|
229
|
+
React.useEffect(
|
|
230
|
+
e => tweener?.watchAxis("scrollY", ( pos ) => doSomething()),
|
|
231
|
+
[tweener]
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
return <ViewBox className={"container"}>
|
|
235
|
+
<Voodoo.Axis
|
|
236
|
+
|
|
237
|
+
id={"scrollY"} // Tween axis Id
|
|
238
|
+
defaultPosition={100} // optional initial position ( default : 0 )
|
|
239
|
+
|
|
240
|
+
// optional Array of tween descriptors with theirs Voodoo.Node target ids ( see axisSample )
|
|
241
|
+
items={tweenArrayWithTargets}
|
|
242
|
+
|
|
243
|
+
// optional size of the scrollable window for drag synchronisation
|
|
244
|
+
scrollableWindow={200}
|
|
245
|
+
|
|
246
|
+
// optional length of this scrollable axis (default to last tween desciptor position+duration)
|
|
247
|
+
size={1000}
|
|
248
|
+
|
|
249
|
+
// optional bounds ( inertia will target them if target pos is out )
|
|
250
|
+
bounds={{ min: 100, max: 900 }}
|
|
251
|
+
|
|
252
|
+
// optional inertia cfg ( false to disable it )
|
|
253
|
+
inertia={
|
|
254
|
+
{
|
|
255
|
+
// called when inertia is updated
|
|
256
|
+
// should return instantaneous move to do if wanted
|
|
257
|
+
shouldLoop: ( currentPos ) => (currentPos > 500 ? -500 : null),
|
|
258
|
+
|
|
259
|
+
// called when inertia know where it will end ( when the user stop dragging )
|
|
260
|
+
willEnd: ( targetPos, targetDelta, duration ) => {
|
|
261
|
+
},
|
|
262
|
+
|
|
263
|
+
// called when inertia know where it will snap ( when the user stop dragging )
|
|
264
|
+
willSnap: ( currentSnapIndex, targetWayPointObj ) => {
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
// called when inertia end
|
|
268
|
+
onStop: ( pos, targetWayPointObj ) => {
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
// called when inertia end on a snap
|
|
272
|
+
onSnap: ( snapIndex, targetWayPointObj ) => {
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
// list of waypoints object ( only support auto snap 50/50 for now )
|
|
276
|
+
wayPoints: [{ at: 100 }, { at: 200 }]
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/>
|
|
280
|
+
|
|
281
|
+
<Voodoo.Node
|
|
282
|
+
id={"testItem"} // optional id
|
|
283
|
+
|
|
284
|
+
style={styleSample}// optional styles applied before any style coming from axes : css syntax + voodoo tweener units & transform management
|
|
285
|
+
|
|
286
|
+
axes={{ scrollY: axisSample }} // optional Array of tween by axis Id with no target node id required ( it will be ignored )
|
|
287
|
+
|
|
288
|
+
onClick={// all unknow props are passed to the child node
|
|
289
|
+
( e ) => {
|
|
290
|
+
// start playing an anim ( prefer scrolling Axes )
|
|
291
|
+
tweener.pushAnim(
|
|
292
|
+
// make all tween target "testItem"
|
|
293
|
+
Voodoo.tools.target(pushIn, "testItem")
|
|
294
|
+
).then(
|
|
295
|
+
( tweenAxis ) => {
|
|
296
|
+
// doSomething next
|
|
297
|
+
}
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
>
|
|
302
|
+
<Voodoo.Draggable
|
|
303
|
+
// make drag y move the scrollAnAxis axis
|
|
304
|
+
// xAxis={ "scrollAnAxis" }
|
|
305
|
+
|
|
306
|
+
// scale / inverse dispatched delta
|
|
307
|
+
// xHook={(delta)=>modify(delta)}
|
|
308
|
+
|
|
309
|
+
// React ref to the box, default to the parent ViewBox
|
|
310
|
+
// scale is as follow : (delta / ((xBoxRef||ViewBox).offsetWidth)) * ( axis.scrollableWindow || axis.duration )
|
|
311
|
+
// xBoxRef={ref}
|
|
312
|
+
|
|
313
|
+
yAxis={"scrollY"}// make drag y move the scrollY axis
|
|
314
|
+
// yHook={(delta)=>modify(delta)}
|
|
315
|
+
// yBoxRef={ref}
|
|
316
|
+
|
|
317
|
+
// mouseDrag={true} // listen for mouse drag ( default to false )
|
|
318
|
+
// touchDrag={false} // listen for touch drag ( default to true )
|
|
319
|
+
|
|
320
|
+
// button={1-3} // limit mouse drag to the specified event.button === ( default to 1; the left btn )
|
|
321
|
+
|
|
322
|
+
// * actually Draggable create it's own div node
|
|
323
|
+
>
|
|
324
|
+
<div>
|
|
325
|
+
Some content to tween
|
|
326
|
+
</div>
|
|
327
|
+
</Voodoo.Draggable>
|
|
328
|
+
</Voodoo.Node>
|
|
329
|
+
</ViewBox>;
|
|
158
330
|
}
|
|
159
331
|
```
|
|
160
332
|
|
|
161
|
-
|
|
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
|
-
```
|
|
333
|
+
For a more complete annotated example with inertia callbacks, `watchAxis`, and programmatic scrolling, see the [full documentation](doc/readme.md).
|
|
184
334
|
|
|
185
335
|
---
|
|
186
336
|
|
|
187
|
-
##
|
|
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?)`
|
|
337
|
+
## Core concepts in 30 seconds
|
|
215
338
|
|
|
216
|
-
|
|
339
|
+
**Axis** — a virtual number line. Move its position (by drag, scroll, or code) and it drives CSS animations on any number of nodes.
|
|
217
340
|
|
|
218
|
-
|
|
341
|
+
**Node** — a React element whose styles are controlled by one or more axes. Style updates go straight to `node.style`, no re-renders.
|
|
219
342
|
|
|
220
|
-
|
|
343
|
+
**Delta composition** — each axis contributes a *change* per frame. Stack a horizontal drag axis and a parallax axis on the same `translateX` and they simply add together. No ownership, no conflicts.
|
|
221
344
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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.
|
|
345
|
+
```
|
|
346
|
+
axis position ──► tween engine ──► Δ per property ──► node.style (direct DOM write)
|
|
347
|
+
▲
|
|
348
|
+
other axes add their Δ here
|
|
349
|
+
```
|
|
233
350
|
|
|
234
351
|
---
|
|
235
352
|
|
|
236
|
-
##
|
|
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:
|
|
353
|
+
## License
|
|
249
354
|
|
|
250
|
-
|
|
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` |
|
|
355
|
+
React-voodoo is dual-licensed:
|
|
256
356
|
|
|
257
|
-
|
|
357
|
+
- **[CC BY-ND 4.0](http://creativecommons.org/licenses/by-nd/4.0)** — use freely in commercial projects; distribution of modified versions is not permitted.
|
|
358
|
+
- **[AGPL v3](http://www.gnu.org/licenses/agpl-3.0)** — distribute modified versions under the same open-source license.
|
|
258
359
|
|
|
259
360
|
---
|
|
260
361
|
|
|
261
|
-
##
|
|
362
|
+
## Support the project
|
|
262
363
|
|
|
263
|
-
|
|
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
|
-
```
|
|
364
|
+
If react-voodoo saved you a day of work, consider supporting it:
|
|
269
365
|
|
|
270
|
-
|
|
366
|
+
[](#)
|
|
271
367
|
|
|
272
|
-
|
|
368
|
+
**BTC** — `bc1qh43j8jh6dr8v3f675jwqq3nqymtsj8pyq0kh5a`
|
|
273
369
|
|
|
274
|
-
|
|
370
|
+
**PayPal** — <a href="https://www.paypal.com/donate/?hosted_button_id=ECHYGKY3GR7CN"><img src="https://img.shields.io/badge/paypal-donate-yellow.svg" alt="PayPal donate" /></a>
|