react-voodoo 2.5.16 → 2.6.0

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.0",
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.1.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,94 @@
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
+ This unlocks a set of features that are unique to the delta model:
47
32
 
48
- ## Samples sources [here](https://github.com/react-voodoo/react-voodoo-samples)
33
+ | Feature | How |
34
+ |---|---|
35
+ | **Additive multi-axis composition** | Each axis contributes a delta; they stack without coordination code. |
36
+ | **Swipeable / draggable animations** | Drag gestures are mapped directly to axis positions with realistic momentum. |
37
+ | **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. |
38
+ | **SSR with correct initial styles** | Axes have a `defaultPosition`; styles are computed server-side and rendered inline — no flash on first paint. |
39
+ | **DOM writes bypass React** | Style updates go straight to `node.style` via direct DOM writes, never triggering a re-render. |
40
+ | **Multi-unit CSS via `calc()`** | Mix `%`, `px`, `vw`, `bw`/`bh` (box-relative units) in a single value — compiled to `calc()` automatically. |
49
41
 
50
- ## You... like it / it saved your day / you stole all the code / you want more?
42
+ ---
51
43
 
52
- [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](#)
44
+ ## Comparison
45
+
46
+ ### Feature matrix
47
+
48
+ | | **react-voodoo** | Framer Motion | GSAP + ScrollTrigger | react-spring | Motion One | anime.js |
49
+ |---|:---:|:---:|:---:|:---:|:---:|:---:|
50
+ | Scroll-linked animation | ✅ | ✅ `useScroll` | ✅ | ⚠️ manual | ✅ | ⚠️ manual |
51
+ | Drag-linked animation | ✅ native | ✅ `drag` | ⚠️ manual | ✅ `@use-gesture` | ⚠️ manual | ❌ |
52
+ | **Additive multi-axis composition** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
53
+ | Physics / momentum inertia | ✅ predictive | ✅ spring | ❌ | ✅ spring | ❌ | ❌ |
54
+ | **Predictive snap target** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
55
+ | **SSR — correct initial styles** | ✅ | ⚠️ flash | ⚠️ flash | ⚠️ flash | ⚠️ flash | ❌ |
56
+ | Bypasses React render loop | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
57
+ | Transform layer composition | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
58
+ | SVG geometry attributes | ✅ | ⚠️ limited | ✅ | ❌ | ⚠️ | ✅ |
59
+ | Multitouch (drag multiple axes) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
60
+ | Bundle size (approx. gzip) | ~18 kB | ~50 kB | ~75 kB | ~30 kB | ~18 kB | ~14 kB |
61
+ | React dependency | ≥ 16 | ≥ 18 | none | ≥ 16 | none | none |
62
+
63
+ ### When to pick react-voodoo
64
+
65
+ - Swipeable carousels and full-page scroll scenes where drag, inertia, and animation must be one system
66
+ - Any UI where **multiple animation "tracks"** compose onto the same elements (parallax, pinned sequences, overlapping effects)
67
+ - **SSR-first** projects where the initial paint must already reflect the animated state
68
+ - Scenarios requiring **predictive callbacks** — e.g. preloading slide N+1 before the swipe animation finishes
53
69
 
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>
70
+ ### When to look elsewhere
56
71
 
57
- ## Basics
72
+ - Simple enter/exit transitions → **Framer Motion** (`AnimatePresence` is excellent for this)
73
+ - Complex GSAP-style timeline sequencing without scroll/drag → **GSAP**
74
+ - Spring-physics micro-interactions → **react-spring** or **Framer Motion**
75
+ - Lightweight imperative animation on non-React pages → **anime.js** or **Motion One**
58
76
 
59
- "all in one" example :
77
+ ---
78
+
79
+ ## Installation
80
+
81
+ ```bash
82
+ npm install react-voodoo
83
+ ```
84
+
85
+ **Peer dependencies:** `react >= 16`, `react-dom >= 16`
86
+
87
+ ---
88
+
89
+ ## All-in-one example
90
+
91
+ Every major feature in a single component, with comments explaining each part.
60
92
 
61
93
  ```jsx harmony
62
94
  import React from "react";
@@ -68,13 +100,13 @@ const styleSample = {
68
100
  * Voodoo.Node style property and the tween descriptors use classic CSS-in-JS declaration
69
101
  * exept we can specify values using the "box" unit which is a [0-1] ratio of the parent ViewBox height / width
70
102
  */
71
-
103
+
72
104
  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
105
+
106
+ // the tweener deal with multiple units
107
+ // it will use css calc fn to add them if there's more than 1 unit used
76
108
  width: ["50%", "10vw", "-50px", ".2box"],
77
-
109
+
78
110
  // transform can use multiple "layers"
79
111
  transform: [
80
112
  {
@@ -85,7 +117,7 @@ const styleSample = {
85
117
  translateZ: "-.2box"
86
118
  }
87
119
  ],
88
-
120
+
89
121
  filter:
90
122
  {
91
123
  blur: "5px"
@@ -97,8 +129,8 @@ const axisSample = [// Examples of tween descriptors
97
129
  from : 0, // tween start position
98
130
  duration: 100, // tween duration
99
131
  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
132
+
133
+ apply: {// relative css values to be applied
102
134
  // Same syntax as the styles
103
135
  transform: [{}, {
104
136
  translateZ: "-.2box"
@@ -108,16 +140,16 @@ const axisSample = [// Examples of tween descriptors
108
140
  {
109
141
  from : 40,
110
142
  duration: 20,
111
-
112
- // triggered when axis has scrolled in the Event period
143
+
144
+ // triggered when axis has scrolled in the Event period
113
145
  // delta : a float value between [-1,1] is the update inside the Event period
114
146
  entering: ( delta ) => false,
115
-
147
+
116
148
  // triggered when axis has scrolled in the Event period
117
149
  // newPos, precPos : float values between [0,1] position inside the Event period
118
150
  // delta : a float value between [-1,1] is the update inside the Event period
119
151
  moving: ( newPos, precPos, delta ) => false,
120
-
152
+
121
153
  // triggered when axis has scrolled out the Event period
122
154
  // delta : a float value between [-1,1] is the update inside the Event period
123
155
  leaving: ( delta ) => false
@@ -125,7 +157,7 @@ const axisSample = [// Examples of tween descriptors
125
157
  ];
126
158
 
127
159
  const Sample = ( {} ) => {
128
-
160
+
129
161
  /**
130
162
  * Voodoo tweener instanciation
131
163
  */
@@ -142,86 +174,86 @@ const Sample = ( {} ) => {
142
174
  maxClickTm: 200,
143
175
  // max drag offset in px before a click become a drag
144
176
  maxClickOffset: 100,
145
- // lock to only 1 drag direction
177
+ // lock to only 1 drag direction
146
178
  dragDirectionLock: false,
147
179
  // allow dragging with mouse
148
180
  enableMouseDrag: false
149
181
  }
150
182
  );
151
- // get a named parent tweener
183
+ // get a named parent tweener
152
184
  const [nammedParentTweener] = Voodoo.hook("root")
153
-
185
+
154
186
  /**
155
187
  * once first render done, axes expose the following values & functions :
156
188
  */
157
189
  // Theirs actual position in :
158
190
  // tweener.axes.(axisId).scrollPos
159
-
191
+
160
192
  // The "scrollTo" function allowing to manually move the axes positions :
161
193
  // tweener.axes.(axisId).scrollTo(targetPos, duration, easeFn)
162
194
  // tweener.scrollTo(targetPos, duration, axisId, easeFn)
163
-
195
+
164
196
  // They can also be watched using the "watchAxis" function;
165
197
  // When called, the returned function will disable the listener if executed :
166
198
  React.useEffect(
167
199
  e => tweener?.watchAxis("scrollY", ( pos ) => doSomething()),
168
200
  [tweener]
169
201
  )
170
-
202
+
171
203
  return <ViewBox className={"container"}>
172
204
  <Voodoo.Axis
173
-
205
+
174
206
  id={"scrollY"} // Tween axis Id
175
207
  defaultPosition={100} // optional initial position ( default : 0 )
176
-
208
+
177
209
  // optional Array of tween descriptors with theirs Voodoo.Node target ids ( see axisSample )
178
210
  items={tweenArrayWithTargets}
179
-
211
+
180
212
  // optional size of the scrollable window for drag synchronisation
181
213
  scrollableWindow={200}
182
-
183
- // optional length of this scrollable axis (default to last tween desciptor position+duration)
214
+
215
+ // optional length of this scrollable axis (default to last tween desciptor position+duration)
184
216
  size={1000}
185
-
217
+
186
218
  // optional bounds ( inertia will target them if target pos is out )
187
219
  bounds={{ min: 100, max: 900 }}
188
-
220
+
189
221
  // optional inertia cfg ( false to disable it )
190
222
  inertia={
191
223
  {
192
224
  // called when inertia is updated
193
225
  // should return instantaneous move to do if wanted
194
226
  shouldLoop: ( currentPos ) => (currentPos > 500 ? -500 : null),
195
-
227
+
196
228
  // called when inertia know where it will end ( when the user stop dragging )
197
229
  willEnd: ( targetPos, targetDelta, duration ) => {
198
230
  },
199
-
231
+
200
232
  // called when inertia know where it will snap ( when the user stop dragging )
201
233
  willSnap: ( currentSnapIndex, targetWayPointObj ) => {
202
234
  },
203
-
235
+
204
236
  // called when inertia end
205
237
  onStop: ( pos, targetWayPointObj ) => {
206
238
  },
207
-
239
+
208
240
  // called when inertia end on a snap
209
241
  onSnap: ( snapIndex, targetWayPointObj ) => {
210
242
  },
211
-
243
+
212
244
  // list of waypoints object ( only support auto snap 50/50 for now )
213
245
  wayPoints: [{ at: 100 }, { at: 200 }]
214
246
  }
215
247
  }
216
248
  />
217
-
249
+
218
250
  <Voodoo.Node
219
251
  id={"testItem"} // optional id
220
-
252
+
221
253
  style={styleSample}// optional styles applied before any style coming from axes : css syntax + voodoo tweener units & transform management
222
-
254
+
223
255
  axes={{ scrollY: axisSample }} // optional Array of tween by axis Id with no target node id required ( it will be ignored )
224
-
256
+
225
257
  onClick={// all unknow props are passed to the child node
226
258
  ( e ) => {
227
259
  // start playing an anim ( prefer scrolling Axes )
@@ -239,23 +271,23 @@ const Sample = ( {} ) => {
239
271
  <Voodoo.Draggable
240
272
  // make drag y move the scrollAnAxis axis
241
273
  // xAxis={ "scrollAnAxis" }
242
-
274
+
243
275
  // 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
-
276
+ // xHook={(delta)=>modify(delta)}
277
+
278
+ // React ref to the box, default to the parent ViewBox
279
+ // scale is as follow : (delta / ((xBoxRef||ViewBox).offsetWidth)) * ( axis.scrollableWindow || axis.duration )
280
+ // xBoxRef={ref}
281
+
250
282
  yAxis={"scrollY"}// make drag y move the scrollY axis
251
283
  // yHook={(delta)=>modify(delta)}
252
- // yBoxRef={ref}
253
-
284
+ // yBoxRef={ref}
285
+
254
286
  // mouseDrag={true} // listen for mouse drag ( default to false )
255
287
  // touchDrag={false} // listen for touch drag ( default to true )
256
-
288
+
257
289
  // button={1-3} // limit mouse drag to the specified event.button === ( default to 1; the left btn )
258
-
290
+
259
291
  // * actually Draggable create it's own div node
260
292
  >
261
293
  <div>
@@ -267,11 +299,40 @@ const Sample = ( {} ) => {
267
299
  }
268
300
  ```
269
301
 
270
- ## License ?
302
+ For a more complete annotated example with inertia callbacks, `watchAxis`, and programmatic scrolling, see the [full documentation](doc/readme.md).
303
+
304
+ ---
305
+
306
+ ## Core concepts in 30 seconds
307
+
308
+ **Axis** — a virtual number line. Move its position (by drag, scroll, or code) and it drives CSS animations on any number of nodes.
309
+
310
+ **Node** — a React element whose styles are controlled by one or more axes. Style updates go straight to `node.style`, no re-renders.
311
+
312
+ **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.
313
+
314
+ ```
315
+ axis position ──► tween engine ──► Δ per property ──► node.style (direct DOM write)
316
+
317
+ other axes add their Δ here
318
+ ```
319
+
320
+ ---
321
+
322
+ ## License
271
323
 
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.
324
+ React-voodoo is dual-licensed:
274
325
 
326
+ - **[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.
327
+ - **[AGPL v3](http://www.gnu.org/licenses/agpl-3.0)** — distribute modified versions under the same open-source license.
328
+
329
+ ---
330
+
331
+ ## Support the project
332
+
333
+ If react-voodoo saved you a day of work, consider supporting it:
275
334
 
276
335
  [![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)](#)
336
+
337
+ **BTC** — `bc1qh43j8jh6dr8v3f675jwqq3nqymtsj8pyq0kh5a`
338
+ **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>