npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-voodoo

v2.6.2

Published

Faster, simplier, additive, swipeable & multidimentional animation engine for React

Readme


Why react-voodoo?

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.

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.

The engine is built on tween-axis and uses its WebAssembly backend for hot-path property accumulation with zero JS-boundary crossings per frame.

This unlocks a set of features that are unique to the delta model:

| Feature | How | |---|---| | Additive multi-axis composition | Each axis contributes a delta; they stack without coordination code. | | Swipeable / draggable animations | Drag gestures are mapped directly to axis positions with realistic momentum. | | 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. | | SSR with correct initial styles | Axes have a defaultPosition; styles are computed server-side and rendered inline — no flash on first paint. | | DOM writes bypass React | Style updates go straight to node.style via direct DOM writes, never triggering a re-render. | | Multi-unit CSS via calc() | Mix %, px, vw, bw/bh (box-relative units) in a single value — compiled to calc() automatically. |


Comparison

Feature matrix

| | react-voodoo | Framer Motion v12 | GSAP + ScrollTrigger | react-spring v10 | Motion v5 | anime.js v4 | |---|:---:|:---:|:---:|:---:|:---:|:---:| | Scroll-linked animation | ✅ | ✅ useScroll | ✅ | ⚠️ manual | ✅ | ✅ ScrollObserver | | Drag-linked animation | ✅ native | ✅ drag | ⚠️ manual | ✅ @use-gesture | ✅ gestures | ✅ Draggable | | Additive multi-axis composition | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | Physics / momentum inertia | ✅ predictive | ✅ spring | ❌ | ✅ spring | ⚠️ limited | ✅ spring | | Predictive snap target | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | SSR — correct initial styles | ✅ | ⚠️ flash | ⚠️ flash | ⚠️ flash | ⚠️ flash | ❌ | | Bypasses React render loop | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Transform layer composition | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | SVG geometry attributes | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | | Multitouch (drag multiple axes) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | Bundle size (approx. gzip) | ~18 kB | ~32 kB | ~35 kB | ~25 kB | ~4 kB | ~10 kB | | React dependency | ≥ 16 | ≥ 18 | none | ≥ 16 | none | none |


Performance

The delta model isn't just an architectural choice — the numbers back it up. In the scenarios that define react-voodoo's use-case — compositing multiple animation sources on the same element — the engine runs 3–7× faster than GSAP and handles far more properties per frame without degrading.

| Scenario | Description | |---|---| | sequential · 5 props | Frame-by-frame advance, 5 CSS properties — the baseline everyone tests | | property scale · 20 props | Same advance with 20 properties — reveals engine scaling cost per property | | 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. | | 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 | | long timeline · 20 segs | A timeline with 20 sequential animation segments — models a full-page scroll sequence or complex keyframe chain |

  • 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).
  • 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.
  • Additive is freegoTo(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.

The one scenario where a stateless interpolator (Framer Motion / Popmotion) wins is a single small axis with easing: pure math functions with no timeline state beat even WASM for trivially short timelines. That overhead is the fair cost of additive composability — and it disappears entirely once you have more than one axis compositing on the same element.

Full benchmark source: perf-compare/bench.js Detailed analysis: doc/Alternatives libs perf comparaison.md


Installation

npm install react-voodoo

Peer dependencies: react >= 16, react-dom >= 16


All-in-one example

Every major feature in a single component, with comments explaining each part.

import React                                  from "react";
import Voodoo                                 from "react-voodoo";
import {itemTweenAxis, tweenArrayWithTargets} from "./somewhere";

const styleSample = {
	/**
	 * Voodoo.Node style property and the tween descriptors use classic CSS-in-JS declaration
	 * exept we can specify values using the "box" unit which is a [0-1] ratio of the parent ViewBox height / width
	 */

	height: "50%",

	// the tweener deal with multiple units
	// it will use css calc fn to add them if there's more than 1 unit used
	width: ["50%", "10vw", "-50px", ".2box"],

	// transform can use multiple "layers"
	transform: [
		{
			// use rotate(X|Y|Z) & translate(X|Y|Z)
			rotateX: "25deg"
		},
		{
			translateZ: "-.2box"
		}
	],

	filter:
		{
			blur: "5px"
		}
};
const axisSample  = [// Examples of tween descriptors
	{
		target  : "someTweenRefId",   // target Voodoo.Node id ( optional if used as parameter on a Voodoo.Node as it will target it )
		from    : 0,                // tween start position
		duration: 100,              // tween duration
		easeFn  : "easeCircleIn",   // function or easing fn id from [d3-ease](https://github.com/d3/d3-ease)

		apply: {// relative css values to be applied
			// Same syntax as the styles
			transform: [{}, {
				translateZ: "-.2box"
			}]
		}
	},
	{
		from    : 40,
		duration: 20,

		// triggered when axis has scrolled in the Event period
		// delta : a float value between [-1,1] is the update inside the Event period
		entering: ( delta ) => false,

		// triggered when axis has scrolled in the Event period
		// newPos, precPos : float values between [0,1] position inside the Event period
		// delta : a float value between  [-1,1] is the update inside the Event period
		moving: ( newPos, precPos, delta ) => false,

		// triggered when axis has scrolled out the Event period
		// delta : a float value between  [-1,1] is the update inside the Event period
		leaving: ( delta ) => false
	}
];

const Sample = ( {} ) => {

	/**
	 * Voodoo tweener instanciation
	 */
		// Classic minimal method
	const [tweener, ViewBox]                   = Voodoo.hook();
	// get the first tweener in parents
	const [parentTweener]                      = Voodoo.hook(true);
	// Create a tweener with options
	const [twenerWithNameAndOptions, ViewBox2] = Voodoo.hook(
		{
			// Give an id to this tweener so we can access it's axes in the childs components
			name: "root",
			// max click tm in ms before a click become a drag
			maxClickTm: 200,
			// max drag offset in px before a click become a drag
			maxClickOffset: 100,
			// lock to only 1 drag direction
			dragDirectionLock: false,
			// allow dragging with mouse
			enableMouseDrag: false
		}
	);
	// get a named parent tweener
	const [nammedParentTweener]                = Voodoo.hook("root")

	/**
	 * once first render done, axes expose the following values & functions :
	 */
	// Theirs actual position in :
	// tweener.axes.(axisId).scrollPos

	// The "scrollTo" function allowing to manually move the axes positions :
	// tweener.axes.(axisId).scrollTo(targetPos, duration, easeFn)
	// tweener.scrollTo(targetPos, duration, axisId, easeFn)

	// They can also be watched using the "watchAxis" function;
	// When called, the returned function will disable the listener if executed :
	React.useEffect(
		e => tweener?.watchAxis("scrollY", ( pos ) => doSomething()),
		[tweener]
	)

	return <ViewBox className={"container"}>
		<Voodoo.Axis

			id={"scrollY"}          // Tween axis Id
			defaultPosition={100}   // optional initial position ( default : 0 )

			// optional Array of tween descriptors with theirs Voodoo.Node target ids ( see axisSample )
			items={tweenArrayWithTargets}

			// optional size of the scrollable window for drag synchronisation
			scrollableWindow={200}

			// optional length of this scrollable axis (default to last tween desciptor position+duration)
			size={1000}

			// optional bounds ( inertia will target them if target pos is out )
			bounds={{ min: 100, max: 900 }}

			// optional inertia cfg ( false to disable it )
			inertia={
				{
					// called when inertia is updated
					// should return instantaneous move to do if wanted
					shouldLoop: ( currentPos ) => (currentPos > 500 ? -500 : null),

					// called when inertia know where it will end ( when the user stop dragging )
					willEnd: ( targetPos, targetDelta, duration ) => {
					},

					// called when inertia know where it will snap ( when the user stop dragging )
					willSnap: ( currentSnapIndex, targetWayPointObj ) => {
					},

					// called when inertia end
					onStop: ( pos, targetWayPointObj ) => {
					},

					// called when inertia end on a snap
					onSnap: ( snapIndex, targetWayPointObj ) => {
					},

					// list of waypoints object ( only support auto snap 50/50 for now )
					wayPoints: [{ at: 100 }, { at: 200 }]
				}
			}
		/>

		<Voodoo.Node
			id={"testItem"} // optional id

			style={styleSample}// optional styles applied before any style coming from axes : css syntax + voodoo tweener units & transform management

			axes={{ scrollY: axisSample }} // optional Array of tween by axis Id with no target node id required ( it will be ignored )

			onClick={// all unknow props are passed to the child node
				( e ) => {
					// start playing an anim ( prefer scrolling Axes )
					tweener.pushAnim(
						// make all tween target "testItem"
						Voodoo.tools.target(pushIn, "testItem")
					).then(
						( tweenAxis ) => {
							// doSomething next
						}
					);
				}
			}
		>
			<Voodoo.Draggable
				// make drag y move the scrollAnAxis axis
				// xAxis={ "scrollAnAxis" }

				// scale / inverse dispatched delta
				// xHook={(delta)=>modify(delta)}

				// React ref to the box, default to the parent ViewBox
				// scale is as follow : (delta / ((xBoxRef||ViewBox).offsetWidth)) * ( axis.scrollableWindow || axis.duration )
				// xBoxRef={ref}

				yAxis={"scrollY"}// make drag y move the scrollY axis
				// yHook={(delta)=>modify(delta)}
				// yBoxRef={ref}

				// mouseDrag={true} // listen for mouse drag ( default to false )
				// touchDrag={false} // listen for touch drag ( default to true )

				// button={1-3} // limit mouse drag to the specified event.button === ( default to 1; the left btn )

				// * actually Draggable create it's own div node
			>
				<div>
					Some content to tween
				</div>
			</Voodoo.Draggable>
		</Voodoo.Node>
	</ViewBox>;
}

For a more complete annotated example with inertia callbacks, watchAxis, and programmatic scrolling, see the full documentation.


Core concepts in 30 seconds

Axis — a virtual number line. Move its position (by drag, scroll, or code) and it drives CSS animations on any number of nodes.

Node — a React element whose styles are controlled by one or more axes. Style updates go straight to node.style, no re-renders.

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.

axis position ──► tween engine ──► Δ per property ──► node.style (direct DOM write)
                                         ▲
                              other axes add their Δ here

License

React-voodoo is dual-licensed:

  • CC BY-ND 4.0 — use freely in commercial projects; distribution of modified versions is not permitted.
  • AGPL v3 — distribute modified versions under the same open-source license.

Support the project

If react-voodoo saved you a day of work, consider supporting it:

contributions welcome

BTCbc1qh43j8jh6dr8v3f675jwqq3nqymtsj8pyq0kh5a

PayPal