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

turbulencejs

v2.0.0

Published

Motion, choreography, effects, and Electron layout coordination for web and desktop applications.

Readme

TurbulenceJS

TurbulenceJS is a zero-runtime-dependency motion system for web and Electron applications. One package provides direct CSS animation, TurbScript choreography, reusable recipes, bounded visual surfaces, accessible interactions, a platform-neutral tween/spring runtime, DOM rendering, and Electron-main layout coordination.

The package verifier measures the core browser bundle and each opt-in recipe pack. Size is reported as an optimization signal rather than used to remove expressive features prematurely.

Install

npm install turbulencejs

Turbulence ships ESM, CommonJS, UMD, and TypeScript declarations from the same package.

Quick start

import turbulencejs from 'turbulencejs';

const button = document.querySelector('button');

button.addEventListener('click', () => {
  turbulencejs.buttons.click(button);
});

const controller = turbulencejs.animate(button, {
  opacity: [0, 1],
  y: [12, 0],
  scale: [0.96, 1]
}, {
  duration: 240,
  easing: 'easeOutCubic',
  onComplete: element => console.log('finished', element)
});

controller.pause();
controller.resume();
controller.reverse();
controller.stop();

All time values, including timeline offsets, are milliseconds.

TurbScript

TurbScript is Turbulence's compositional animation language. A Turb is an immutable, reusable animation description; script() resolves targets and turns that description into one lifecycle-owned performance.

import { turb, script } from 'turbulencejs';
import { bubbleIn, skedaddle } from 'turbulencejs/cartoon';

const entrance = turb.sequence(
  turb.stagger(45, turb.choose([
    bubbleIn({ intensity: 0.8 }),
    skedaddle.in({ from: 'left' })
  ])),
  turb.slot('actions', turb.track({ opacity: [0, 1], y: [8, 0] }))
);

const performance = script(entrance).play(() =>
  [...document.querySelectorAll('.card')].map(target => ({
    target,
    slots: { actions: '[data-card-actions]' }
  })),
  { seed: 'quarterly-chaos' }
);

performance.pause().resume();
performance.stop();  // preserve the interrupted frame
performance.reset(); // restore styles owned by this performance
performance.replay();

The fluent director is ergonomic sugar over the same graph and runtime:

import { direct } from 'turbulencejs';

direct('.card')
  .stagger(45)
  .using((target, index) => index % 2 ? bubbleIn() : skedaddle.in())
  .then(turb.track({ opacity: 1 }))
  .seed('quarterly-chaos')
  .play();

See the TurbScript language guide for grammar, targets, determinism, lifecycle, errors, and accessibility, and the recipe guide for the cartoon, cinematic, subtle, and extreme packs.

Choose the right layer

| Need | Start with | |---|---| | Animate CSS properties on one element | animate() | | Coordinate reusable time-based choreography | turb + script() | | Author a canvas/game/pixel renderer on Turbulence's clock | turb.driver() | | Emit a safe host workflow phase | turb.mark() + performance.on('mark') | | Capture/render bounded raster surfaces | turbulencejs/surfaces | | Use Snaporate, Enhance, Sidebar Ready, or Tetris Load | turbulencejs/effects | | Own hover/focus or drag/drop motion | turbulencejs/interact | | Drive generic values with an injected clock | Turbulence or turbulencejs/runtime | | Animate DOM transform/style channels with retargeting | turbulencejs/dom | | Animate Electron bounds or synchronized regions | turbulencejs/main |

Property tracks and custom drivers share one timeline. A driver receives deterministic progress and a lifecycle scope; it must not schedule its own animation frame or commit application state. Named marks let host code mount or commit content without running arbitrary callbacks inside compile/render. See advanced drivers and mark semantics.

Generic runtime

The root preserves the platform-neutral Turbulence engine. Supply a driver explicitly; the engine never assumes a browser or Electron process.

import { Turbulence, manualDriver } from 'turbulencejs';

const driver = manualDriver();
const engine = new Turbulence({ driver });
const tween = engine.tween({
  from: { x: 0, opacity: 0 },
  to: { x: 120, opacity: 1 },
  duration: 240,
  easing: 'smooth',
  onUpdate: value => render(value)
});

driver.step(120);
tween.retarget({ x: 40, opacity: 1 });
driver.step(240);
await tween.finished; // always resolves; cancellation does not reject
engine.dispose();

Turbulence, Tween, Spring, Ticker, rafDriver, timerDriver, manualDriver, interpolation helpers, and choreography functions are available from both the root and turbulencejs/runtime. The root easing namespace contains strict resolve() plus the browser-oriented createEasing() parser.

DOM and Electron

Renderer code can animate independent transform, opacity, custom-property, and explicitly seeded style channels. Repeated calls retarget the in-flight channel without a visual jump.

import { createEngine, DomAnimator } from 'turbulencejs/dom';

const engine = createEngine();
const animator = new DomAnimator(engine);
const entrance = animator.animate(panel, { x: 0, opacity: 1 }, {
  from: { opacity: 0 },
  duration: 180,
  easing: 'snappy'
});

await entrance.finished;
animator.dispose();
engine.dispose(); // also removes the reduced-motion media-query listener

Electron main-process code uses timer-driven bounds and layout adapters with no browser-global access:

import { createEngine, BoundsAnimator, Layout } from 'turbulencejs/main';

const engine = createEngine({ fps: 60 });
const bounds = new BoundsAnimator(engine);
await bounds.animate(window, nextBounds, { duration: 240 }).finished;

const layout = new Layout(engine, state => ({
  sidebar: { x: 0, y: 0, width: state.sidebar, height: state.height },
  content: { x: state.sidebar, y: 0, width: state.width - state.sidebar, height: state.height }
}));
layout.onRegion('content', rect => view.setBounds(rect));
layout.set(initialState);
layout.animateTo(expandedState);

See Electron process and lifecycle guidance and the package entrypoint matrix.

Agent-assisted integration

The npm package includes a self-contained turbulencejs-integration agent skill. It inspects a web or Electron project, lets the user choose entrypoints, animation style, and intensity from restrained through theatrical, implements the approved selection, verifies reduced motion and lifecycle cleanup, and records the work under agent-work/{slug}/turbulencejs-integration/.

Copy skills/turbulencejs-integration into the skill directory used by your agent host. It includes its own references, work-artifact contract, templates, selection catalog, and project inspector; gremlin-skills is not required. See the agent skill guide for installation, invocation examples, architecture, and verification.

Optional visual and interaction runtimes

import { script } from 'turbulencejs';
import { source } from 'turbulencejs/surfaces';
import { snaporate, sidebarReady } from 'turbulencejs/effects';
import { drag, hover } from 'turbulencejs/interact';

script(snaporate.out(source.image(image), {
  fidelity: 'pixel',
  direction: 'up-right'
})).play(card, { seed: 'public-demo' });

const hoverSession = hover(card, { enter: lift, leave: settle });
const dragSession = drag(rows, {
  axis: 'y',
  dropZones: rows,
  canDrop: ({ target, zone }) => target !== zone,
  onDrop: ({ target, zone }) => reorderInHostState(target, zone)
});

// Component teardown:
hoverSession.destroy();
dragSession.destroy();

Optional packs are externalized from the core UMD and ship their own ESM, CommonJS, and declarations. turbulencejs/surfaces adds strong raster descriptors, truthful supported-subset element capture, DOM/Canvas2D/WebGL2/worker renderers, and finite resource policy. turbulencejs/effects contains the expressive recipes. turbulencejs/interact owns listeners, performances, pointer capture, focus, and cleanup while the host retains business validity and data commits.

Core animation

animate(element, properties, options) accepts numeric or CSS-string targets and arrays of keyframes. Transform aliases x, y, and z map to translateX, translateY, and translateZ. Supported transform properties are translation, rotation, scale, and skew axes.

import { animate } from 'turbulencejs';

animate(card, {
  x: [0, 40, 0],
  rotate: [0, 4, 0],
  backgroundColor: ['#fff', '#dbeafe']
}, {
  duration: 500,
  delay: 50,
  repeat: 1,
  yoyo: true,
  easing: 'easeOutElastic(1, 0.7)',
  onStart: element => {},
  onUpdate: (element, progress, iteration) => {},
  onComplete: element => {},
  onCancel: element => {}
});

Known transform components are composed, so animating one component does not discard unrelated translation, rotation, or scale values. Zero-duration animations apply their final state immediately and invoke lifecycle callbacks in a microtask.

Easing

import { easing, animate } from 'turbulencejs';

animate(panel, { opacity: [0, 1] }, {
  easing: easing.elasticOut
});

const custom = easing.createEasing('easeOutElastic(1, 0.6)');

Canonical names such as elasticOut, preset-style aliases such as easeOutElastic, parameterized strings, and easing functions are accepted. Unknown strings warn once and fall back to linear.

Available families are linear, basic ease, quad, cubic, quart, quint, sine, expo, circ, elastic, back, and bounce, with In, Out, and InOut variants where applicable.

Timelines

import { animate, timeline } from 'turbulencejs';

const sequence = timeline.create({ timeScale: 1 })
  .add(animate(title, { opacity: [0, 1] }, { duration: 200 }), 0)
  .add(animate(body, { y: [12, 0], opacity: [0, 1] }, { duration: 240 }), '+=40');

sequence
  .onComplete(() => console.log('timeline complete'))
  .play();

sequence.pause().resume().seek(120).reverse();

Adding an animation transfers it to the timeline before its first frame. Timelines use the same compiled values and easing functions as the core engine.

Springs

import { spring } from 'turbulencejs';

const motion = spring.to(card, 'x', 200, spring.bouncy());
motion.retarget(80);
motion.stop();

Spring transform properties are x, y, z, scale axes, and rotation axes. Non-transform numeric CSS properties are also supported. Integration uses bounded substeps so long frames do not create an unbounded physics jump.

Presets: spring.wobbly(), spring.bouncy(), and spring.gentle().

Motion paths

import { path } from 'turbulencejs';

const route = path.create([
  { x: 0, y: 0 },
  { x: 80, y: 40 },
  { x: 160, y: 0 }
]);

path.follow(badge, route, {
  duration: 800,
  rotate: true,
  easing: 'cubicInOut'
});

Paths are compiled and measured once per animation. Absolute, relative, and repeated M, L, H, V, C, Q, and Z commands are supported. A, S, and T are rejected with an explicit error instead of being approximated incorrectly. Path translation and tangent rotation preserve unrelated transforms.

Reduced motion

Turbulence respects prefers-reduced-motion: reduce by default. Core, timeline, spring, and path animations immediately apply their semantic final state, then run normal completion and cleanup in a microtask.

Set respectReducedMotion: false on an individual animation only when motion is essential and an equivalent accessible result is unavailable.

For deterministic previews and tests, reducedMotion: true | false explicitly overrides the media query on an animation or Turb performance. Surface recipes skip capture/allocation when reduced; interactions preserve pointer/keyboard semantics and host callbacks while removing follow/settle motion.

Semantic motion roles

motion.roles exposes shared recipes for feedbackFast, stateEnter, stateExit, spatialMove, emphasizedExplain, gestureSettle, ambient, and celebration. Every role declares an instant reduced-motion alternative.

import { animate, motion } from 'turbulencejs';

animate(element, { opacity: [0, 1] }, motion.options('stateEnter'));

Presets

  • buttons: jelly, explode, springCrazy, vibrate, glowPulse, flip, morph, shockwave, chaos, boing, rubberBand, poof, bounce, shimmer, ripple, click
  • forms: shake, highlight, successBounce, floatingLabel, validationFeedback, inputFocusZoom, labelHop, errorWobble
  • toasts: slideInBounce, zoomBurst, wiggleIn, heartbeat, flipIn, dropBounce, tada, slinkyIn, popAndWiggle, arcIn
  • dialogs: elasticZoom, explosiveZoom, slam, flip3D, swipe, bounceIn, helicopterIn, glitchIn, paperFold, cartoonRunIn, dropAndSquash
  • dropdowns: explosiveExpand, fanItems, rotate3D, floatingPanel, dramaticSwipe, cardDeck, cascadeItems, unroll
  • loading: spinner, dots, pulse, progress, typing

Preset controllers support interruption. Temporary nodes, timers, and host-style mutations owned by composite effects are cleaned up when the effect completes or is stopped.

Development

npm test -- --runInBand
npm run lint
npm run motion:check
npm run build
npm run verify:package
npm run verify:consumers
ELECTRON_BIN=/path/to/electron npm run verify:electron

The package verifier packs the actual publishable artifact, instantiates every public entry through ESM and CommonJS, checks declarations and the worker asset, rejects duplicate runtimes in optional entries, and reports raw plus gzip sizes. Consumer verification installs that tarball into isolated ESM, CommonJS, and Vite projects. Electron verification requires an Electron executable through ELECTRON_BIN (or on PATH).

SaaS motion lab

The Vite showcase is a polished, static SaaS dashboard used to compare Turbulence motion in realistic interface context. Its command console can replay the page with Quiet, Waterfall, Sproing, Slam, Glitch, and Zero Gravity profiles at three intensity levels. The Turbulence desk proves Snaporate, Enhance error/retry, Sidebar Ready, Tetris Load, hover, and pointer/keyboard client reordering through public package exports.

npm run build
npm --prefix examples/showcase install
npm --prefix examples/showcase run dev

Run npm run showcase:verify for its tests, lint, root build, and nested production build. See examples/showcase/README.md for keyboard controls, reproducible URL settings, interaction coverage, and the experiment-extension contract.

Browser support

Turbulence targets the browsers in package.json: greater than 1% usage, the last two browser versions, and maintained browsers. Consumers must provide requestAnimationFrame, performance.now, and matchMedia where those APIs are unavailable.

License

MIT