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/jest.config.js ADDED
@@ -0,0 +1,16 @@
1
+ module.exports = {
2
+ testEnvironment: 'jest-environment-jsdom',
3
+
4
+ setupFilesAfterEnv: ['<rootDir>/etc/tests/jest.setup.js'],
5
+
6
+ transform: {
7
+ '^.+\\.[jt]sx?$': 'babel-jest',
8
+ },
9
+
10
+ testMatch: ['<rootDir>/etc/tests/**/*.test.{js,jsx}'],
11
+
12
+ // tween-axis ships a CJS dist — no transformation needed.
13
+ // d3-ease and color-rgba expose CJS-compatible builds.
14
+ // The local dist/ bundle is already compiled CJS — skip babel on it too.
15
+ transformIgnorePatterns: ['/node_modules/', '/dist/'],
16
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-voodoo",
3
- "version": "2.5.16",
3
+ "version": "2.6.1",
4
4
  "license": "(CC-BY-ND-4.0 OR AGPL-3.0-only)",
5
5
  "main": "./dist/react-voodoo.js",
6
6
  "author": "Nathan Braun <n8tz.js@gmail.com>",
@@ -9,8 +9,9 @@
9
9
  "scripts": {
10
10
  "build": "lpack",
11
11
  "devLib": "lpack :staging -w",
12
- "test": "echo ok",
13
- "testAuto": "nodemon --watch ./dist/react-voodoo.js --watch ./etc/tests/**/*.js --exec \"npm test\"",
12
+ "test": "jest",
13
+ "test:watch": "jest --watch",
14
+ "testAuto": "nodemon --watch ./src --watch ./etc/tests --exec \"npm test\"",
14
15
  "setupLayers": "lpack-setup"
15
16
  },
16
17
  "peerDependencies": {
@@ -25,13 +26,18 @@
25
26
  "fast-deep-equal": "^3.1.3",
26
27
  "is": "^3.3.0",
27
28
  "shortid": "^2.2.16",
28
- "tween-axis": "^2.0.3"
29
+ "tween-axis": "^2.2.0"
29
30
  },
30
31
  "devDependencies": {
32
+ "@babel/preset-env": "^7.24.0",
33
+ "@babel/preset-react": "^7.24.0",
31
34
  "@babel/register": "^7.24.6",
32
35
  "@testing-library/jest-dom": "^6.4.6",
33
36
  "@testing-library/react": "^16.0.0",
37
+ "babel-jest": "^29.0.0",
34
38
  "docdash": "^2.0.2",
39
+ "jest": "^29.0.0",
40
+ "jest-environment-jsdom": "^29.0.0",
35
41
  "jsdoc": "^4.0.3",
36
42
  "layer-pack": "^3.0.7",
37
43
  "lpack-react": "^2.2.0",
package/readme.md CHANGED
@@ -1,62 +1,96 @@
1
1
  <h1 align="center">react-voodoo</h1>
2
- <p align="center">Fast, SSR ready, additive & swipeable, tween composition engine for React</p>
3
-
4
- ___
5
- <p align="center"><img width="192" src ="https://github.com/react-voodoo/react-voodoo/raw/master/doc/assets/logo-v0.png?sanitize=true" /></p>
2
+ <p align="center"><b>Additive · Swipeable · SSR-ready · Physics-based</b><br/>A delta-driven tween composition engine for React</p>
6
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>
7
5
 
8
6
  <p align="center">
9
- <a href="https://www.npmjs.com/package/react-voodoo">
10
- <img src="https://img.shields.io/npm/v/react-voodoo.svg" alt="Npm version" /></a>
11
- <a href="https://travis-ci.org/react-voodoo/react-voodoo">
12
- <img src="https://travis-ci.org/react-voodoo/react-voodoo.svg?branch=master" alt="Build Status" /></a>
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>
13
9
  <img src="https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat" />
14
10
  <br/>
15
- <a href="http://creativecommons.org/licenses/by-nd/4.0">
16
- <img src="https://img.shields.io/badge/License-CC%20BY--ND%204.0-lightgrey.svg" alt="License: CC BY-ND 4.0" /></a>
17
- <a href="http://www.gnu.org/licenses/agpl-3.0">
18
- <img src="https://img.shields.io/badge/License-AGPL%20v3-blue.svg" alt="License: AGPL v3" /></a>
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>
19
13
  </p>
20
14
 
21
- ## Why another animation engine ?
15
+ <p align="center">
16
+ <a href="doc/readme.md"><b>Full documentation</b></a> &nbsp;·&nbsp;
17
+ <a href="https://react-voodoo.github.io/react-voodoo-samples/"><b>Live demos & CodeSandbox</b></a> &nbsp;·&nbsp;
18
+ <a href="https://github.com/react-voodoo/react-voodoo-samples"><b>Sample sources</b></a>
19
+ </p>
22
20
 
23
- Classic Tween engines can only output absolute values, which quickly results in very complex code when we have to
24
- gradually compose values from multiple sources (e.g. when merging multiple animations based on user drag interactions )
25
- .<br/>
21
+ <p align="center"><img src="https://github.com/react-voodoo/react-voodoo/raw/master/doc/assets/demo.gif?sanitize=true" /></p>
26
22
 
27
- React-Voodoo use a delta-based interpolation engine that solves this problem, it allows:
23
+ ---
28
24
 
29
- - Additive tween
30
- - Swipeable complex animations ( like Android & iOS )
31
- - Fast & direct DOM updates ( not bound to the React rendering loop )
32
- - Server Side Rendering of any scroll / swipe position
33
- - Easily connect sensors / gestures to complex animations
34
- - Hot switching scrollable anims ( responsive )
35
- - Predictive inertia ( knowing where inertia will stop while animating )
36
- - Multitouch dragging ( drag multiple things at once )
37
- - Intuitive & flexible animation system
38
- - Cool ( & performant ) React integration
39
- - Automatically deal with multiple units using css "calc( ... )"
40
- - etc...
25
+ ## Why react-voodoo?
41
26
 
42
- ## Basic documentation [here](doc/readme.md)
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.
43
28
 
44
- ## Live demo & codesandbox [here](https://react-voodoo.github.io/react-voodoo-samples/)
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.
45
30
 
46
- <p align="center"><img src ="https://github.com/react-voodoo/react-voodoo/raw/master/doc/assets/demo.gif?sanitize=true" /></p>
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.
47
32
 
48
- ## Samples sources [here](https://github.com/react-voodoo/react-voodoo-samples)
33
+ This unlocks a set of features that are unique to the delta model:
49
34
 
50
- ## You... like it / it saved your day / you stole all the code / you want more?
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. |
51
43
 
52
- [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](#)
44
+ ---
45
+
46
+ ## Comparison
47
+
48
+ ### Feature matrix
49
+
50
+ | | **react-voodoo** | Framer Motion | GSAP + ScrollTrigger | react-spring | Motion One | anime.js |
51
+ |---|:---:|:---:|:---:|:---:|:---:|:---:|
52
+ | Scroll-linked animation | ✅ | ✅ `useScroll` | ✅ | ⚠️ manual | ✅ | ⚠️ manual |
53
+ | Drag-linked animation | ✅ native | ✅ `drag` | ⚠️ manual | ✅ `@use-gesture` | ⚠️ manual | ❌ |
54
+ | **Additive multi-axis composition** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
55
+ | Physics / momentum inertia | ✅ predictive | ✅ spring | ❌ | ✅ 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 | ✅ | ⚠️ limited | ✅ | ❌ | ⚠️ | ✅ |
61
+ | Multitouch (drag multiple axes) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
62
+ | Bundle size (approx. gzip) | ~18 kB | ~50 kB | ~75 kB | ~30 kB | ~18 kB | ~14 kB |
63
+ | React dependency | ≥ 16 | ≥ 18 | none | ≥ 16 | none | none |
64
+
65
+ ### When to pick react-voodoo
66
+
67
+ - Swipeable carousels and full-page scroll scenes where drag, inertia, and animation must be one system
68
+ - Any UI where **multiple animation "tracks"** compose onto the same elements (parallax, pinned sequences, overlapping effects)
69
+ - **SSR-first** projects where the initial paint must already reflect the animated state
70
+ - Scenarios requiring **predictive callbacks** — e.g. preloading slide N+1 before the swipe animation finishes
53
71
 
54
- BTC : bc1qh43j8jh6dr8v3f675jwqq3nqymtsj8pyq0kh5a<br/>
55
- Paypal : <span class="badge-paypal"><a href="https://www.paypal.com/donate/?hosted_button_id=ECHYGKY3GR7CN" title="Donate to this project using Paypal"><img src="https://img.shields.io/badge/paypal-donate-yellow.svg" alt="PayPal donate button" /></a></span>
72
+ ### When to look elsewhere
56
73
 
57
- ## Basics
74
+ - Simple enter/exit transitions → **Framer Motion** (`AnimatePresence` is excellent for this)
75
+ - Complex GSAP-style timeline sequencing without scroll/drag → **GSAP**
76
+ - Spring-physics micro-interactions → **react-spring** or **Framer Motion**
77
+ - Lightweight imperative animation on non-React pages → **anime.js** or **Motion One**
58
78
 
59
- "all in one" example :
79
+ ---
80
+
81
+ ## Installation
82
+
83
+ ```bash
84
+ npm install react-voodoo
85
+ ```
86
+
87
+ **Peer dependencies:** `react >= 16`, `react-dom >= 16`
88
+
89
+ ---
90
+
91
+ ## All-in-one example
92
+
93
+ Every major feature in a single component, with comments explaining each part.
60
94
 
61
95
  ```jsx harmony
62
96
  import React from "react";
@@ -68,13 +102,13 @@ const styleSample = {
68
102
  * Voodoo.Node style property and the tween descriptors use classic CSS-in-JS declaration
69
103
  * exept we can specify values using the "box" unit which is a [0-1] ratio of the parent ViewBox height / width
70
104
  */
71
-
105
+
72
106
  height: "50%",
73
-
74
- // the tweener deal with multiple units
75
- // it will use css calc fn to add them if there's more than 1 unit used
107
+
108
+ // the tweener deal with multiple units
109
+ // it will use css calc fn to add them if there's more than 1 unit used
76
110
  width: ["50%", "10vw", "-50px", ".2box"],
77
-
111
+
78
112
  // transform can use multiple "layers"
79
113
  transform: [
80
114
  {
@@ -85,7 +119,7 @@ const styleSample = {
85
119
  translateZ: "-.2box"
86
120
  }
87
121
  ],
88
-
122
+
89
123
  filter:
90
124
  {
91
125
  blur: "5px"
@@ -97,8 +131,8 @@ const axisSample = [// Examples of tween descriptors
97
131
  from : 0, // tween start position
98
132
  duration: 100, // tween duration
99
133
  easeFn : "easeCircleIn", // function or easing fn id from [d3-ease](https://github.com/d3/d3-ease)
100
-
101
- apply: {// relative css values to be applied
134
+
135
+ apply: {// relative css values to be applied
102
136
  // Same syntax as the styles
103
137
  transform: [{}, {
104
138
  translateZ: "-.2box"
@@ -108,16 +142,16 @@ const axisSample = [// Examples of tween descriptors
108
142
  {
109
143
  from : 40,
110
144
  duration: 20,
111
-
112
- // triggered when axis has scrolled in the Event period
145
+
146
+ // triggered when axis has scrolled in the Event period
113
147
  // delta : a float value between [-1,1] is the update inside the Event period
114
148
  entering: ( delta ) => false,
115
-
149
+
116
150
  // triggered when axis has scrolled in the Event period
117
151
  // newPos, precPos : float values between [0,1] position inside the Event period
118
152
  // delta : a float value between [-1,1] is the update inside the Event period
119
153
  moving: ( newPos, precPos, delta ) => false,
120
-
154
+
121
155
  // triggered when axis has scrolled out the Event period
122
156
  // delta : a float value between [-1,1] is the update inside the Event period
123
157
  leaving: ( delta ) => false
@@ -125,7 +159,7 @@ const axisSample = [// Examples of tween descriptors
125
159
  ];
126
160
 
127
161
  const Sample = ( {} ) => {
128
-
162
+
129
163
  /**
130
164
  * Voodoo tweener instanciation
131
165
  */
@@ -142,86 +176,86 @@ const Sample = ( {} ) => {
142
176
  maxClickTm: 200,
143
177
  // max drag offset in px before a click become a drag
144
178
  maxClickOffset: 100,
145
- // lock to only 1 drag direction
179
+ // lock to only 1 drag direction
146
180
  dragDirectionLock: false,
147
181
  // allow dragging with mouse
148
182
  enableMouseDrag: false
149
183
  }
150
184
  );
151
- // get a named parent tweener
185
+ // get a named parent tweener
152
186
  const [nammedParentTweener] = Voodoo.hook("root")
153
-
187
+
154
188
  /**
155
189
  * once first render done, axes expose the following values & functions :
156
190
  */
157
191
  // Theirs actual position in :
158
192
  // tweener.axes.(axisId).scrollPos
159
-
193
+
160
194
  // The "scrollTo" function allowing to manually move the axes positions :
161
195
  // tweener.axes.(axisId).scrollTo(targetPos, duration, easeFn)
162
196
  // tweener.scrollTo(targetPos, duration, axisId, easeFn)
163
-
197
+
164
198
  // They can also be watched using the "watchAxis" function;
165
199
  // When called, the returned function will disable the listener if executed :
166
200
  React.useEffect(
167
201
  e => tweener?.watchAxis("scrollY", ( pos ) => doSomething()),
168
202
  [tweener]
169
203
  )
170
-
204
+
171
205
  return <ViewBox className={"container"}>
172
206
  <Voodoo.Axis
173
-
207
+
174
208
  id={"scrollY"} // Tween axis Id
175
209
  defaultPosition={100} // optional initial position ( default : 0 )
176
-
210
+
177
211
  // optional Array of tween descriptors with theirs Voodoo.Node target ids ( see axisSample )
178
212
  items={tweenArrayWithTargets}
179
-
213
+
180
214
  // optional size of the scrollable window for drag synchronisation
181
215
  scrollableWindow={200}
182
-
183
- // optional length of this scrollable axis (default to last tween desciptor position+duration)
216
+
217
+ // optional length of this scrollable axis (default to last tween desciptor position+duration)
184
218
  size={1000}
185
-
219
+
186
220
  // optional bounds ( inertia will target them if target pos is out )
187
221
  bounds={{ min: 100, max: 900 }}
188
-
222
+
189
223
  // optional inertia cfg ( false to disable it )
190
224
  inertia={
191
225
  {
192
226
  // called when inertia is updated
193
227
  // should return instantaneous move to do if wanted
194
228
  shouldLoop: ( currentPos ) => (currentPos > 500 ? -500 : null),
195
-
229
+
196
230
  // called when inertia know where it will end ( when the user stop dragging )
197
231
  willEnd: ( targetPos, targetDelta, duration ) => {
198
232
  },
199
-
233
+
200
234
  // called when inertia know where it will snap ( when the user stop dragging )
201
235
  willSnap: ( currentSnapIndex, targetWayPointObj ) => {
202
236
  },
203
-
237
+
204
238
  // called when inertia end
205
239
  onStop: ( pos, targetWayPointObj ) => {
206
240
  },
207
-
241
+
208
242
  // called when inertia end on a snap
209
243
  onSnap: ( snapIndex, targetWayPointObj ) => {
210
244
  },
211
-
245
+
212
246
  // list of waypoints object ( only support auto snap 50/50 for now )
213
247
  wayPoints: [{ at: 100 }, { at: 200 }]
214
248
  }
215
249
  }
216
250
  />
217
-
251
+
218
252
  <Voodoo.Node
219
253
  id={"testItem"} // optional id
220
-
254
+
221
255
  style={styleSample}// optional styles applied before any style coming from axes : css syntax + voodoo tweener units & transform management
222
-
256
+
223
257
  axes={{ scrollY: axisSample }} // optional Array of tween by axis Id with no target node id required ( it will be ignored )
224
-
258
+
225
259
  onClick={// all unknow props are passed to the child node
226
260
  ( e ) => {
227
261
  // start playing an anim ( prefer scrolling Axes )
@@ -239,23 +273,23 @@ const Sample = ( {} ) => {
239
273
  <Voodoo.Draggable
240
274
  // make drag y move the scrollAnAxis axis
241
275
  // xAxis={ "scrollAnAxis" }
242
-
276
+
243
277
  // scale / inverse dispatched delta
244
- // xHook={(delta)=>modify(delta)}
245
-
246
- // React ref to the box, default to the parent ViewBox
247
- // scale is as follow : (delta / ((xBoxRef||ViewBox).offsetWidth)) * ( axis.scrollableWindow || axis.duration )
248
- // xBoxRef={ref}
249
-
278
+ // xHook={(delta)=>modify(delta)}
279
+
280
+ // React ref to the box, default to the parent ViewBox
281
+ // scale is as follow : (delta / ((xBoxRef||ViewBox).offsetWidth)) * ( axis.scrollableWindow || axis.duration )
282
+ // xBoxRef={ref}
283
+
250
284
  yAxis={"scrollY"}// make drag y move the scrollY axis
251
285
  // yHook={(delta)=>modify(delta)}
252
- // yBoxRef={ref}
253
-
286
+ // yBoxRef={ref}
287
+
254
288
  // mouseDrag={true} // listen for mouse drag ( default to false )
255
289
  // touchDrag={false} // listen for touch drag ( default to true )
256
-
290
+
257
291
  // button={1-3} // limit mouse drag to the specified event.button === ( default to 1; the left btn )
258
-
292
+
259
293
  // * actually Draggable create it's own div node
260
294
  >
261
295
  <div>
@@ -267,11 +301,40 @@ const Sample = ( {} ) => {
267
301
  }
268
302
  ```
269
303
 
270
- ## License ?
304
+ For a more complete annotated example with inertia callbacks, `watchAxis`, and programmatic scrolling, see the [full documentation](doc/readme.md).
305
+
306
+ ---
307
+
308
+ ## Core concepts in 30 seconds
309
+
310
+ **Axis** — a virtual number line. Move its position (by drag, scroll, or code) and it drives CSS animations on any number of nodes.
311
+
312
+ **Node** — a React element whose styles are controlled by one or more axes. Style updates go straight to `node.style`, no re-renders.
313
+
314
+ **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.
315
+
316
+ ```
317
+ axis position ──► tween engine ──► Δ per property ──► node.style (direct DOM write)
318
+
319
+ other axes add their Δ here
320
+ ```
321
+
322
+ ---
323
+
324
+ ## License
271
325
 
272
- Using CC BY-ND, you can use it in commercial apps, but you can't distribute modified versions.<br/>
273
- Using AGPL, you can distribute modified versions but theses versions must be AGPL too.
326
+ React-voodoo is dual-licensed:
274
327
 
328
+ - **[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.
329
+ - **[AGPL v3](http://www.gnu.org/licenses/agpl-3.0)** — distribute modified versions under the same open-source license.
330
+
331
+ ---
332
+
333
+ ## Support the project
334
+
335
+ If react-voodoo saved you a day of work, consider supporting it:
275
336
 
276
337
  [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](#)
277
- [![*](https://www.google-analytics.com/collect?v=1&tid=UA-82058889-1&cid=555&t=event&ec=project&ea=view&dp=%2Fproject%2Freact-voodoo&dt=readme)](#)
338
+
339
+ **BTC** — `bc1qh43j8jh6dr8v3f675jwqq3nqymtsj8pyq0kh5a`
340
+ **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>