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

repond-movers

v1.2.0

Published

movers for repond

Downloads

42

Readme

Repond-Movers

Physics-based animation library for Repond

Smooth, realistic animations using spring physics and friction-based sliding - built to integrate seamlessly with Repond's state management and effects system.

npm version License: MIT


Why Repond-Movers?

Repond-movers provides frame-aware, physics-driven animations that feel natural and responsive:

  • Realistic physics: Spring oscillation and friction-based sliding
  • Frame-rate independent: Smooth animations at any FPS
  • Multiple dimensions: 1D scalars, 2D points, 3D positions, or multiple named values
  • Declarative API: Set a goal and let physics handle the rest
  • Time control: Slow down, speed up, or pause animations via state
  • Type-safe: Full TypeScript support with Repond's type system

Perfect for:

  • Drag & drop with momentum and spring-back
  • 3D game character movements
  • Smooth UI transitions and animations
  • Camera movements and easing
  • Character bone/blend shape animations

Quick Start

Installation

npm install repond-movers repond chootils

Basic Example

import { addItem, setState } from "repond";
import { initMovers, addMoverEffects, moverState, moverRefs } from "repond-movers";

// 1. Setup (optional: enable time control)
initMovers(["game", "ticker", "elapsed"]);

// 2. Create store with mover
const spriteStore = {
  newState: () => ({
    ...moverState("position", { moveMode: "spring" }),
  }),
  newRefs: () => ({
    ...moverRefs("position", { mass: 50, stiffness: 15, damping: 9 }),
  }),
};

// 3. Add mover effects
addMoverEffects("sprite", "position", "2d");

// 4. Use it!
addItem("sprite", "sprite1");
setState("sprite", { positionGoal: { x: 100, y: 50 } }, "sprite1");
// Position smoothly animates to (100, 50) with spring physics!

Core Concepts

1. Movers

A "mover" continuously animates a value toward a goal using physics. Four types available:

| Type | Dimensions | Use Case | |------|-----------|----------| | 1D | Single scalar | Rotation, opacity, progress bars | | 2D | x, y coordinates | Screen positions, 2D game movement | | 3D | x, y, z coordinates | 3D world positions, camera movement | | Multi | Multiple named 1D values | Character bone animations, blend shapes |

2. Physics Modes

| Mode | Behavior | Use Case | |------|----------|----------| | Spring | Oscillates toward target with damping | Natural movements, bouncy animations | | Slide | Friction-based deceleration | Momentum after drag, sliding panels | | Drag | (Reserved) Manual dragging with velocity tracking | Drag & drop interactions | | Push | (Reserved) Constant-speed movement | WASD-style movement |

3. Generated State Properties

When you create a mover named "position", you get these state properties:

  • position: Current animated value
  • positionGoal: Target value to animate toward
  • positionIsMoving: Boolean flag (true while animating)
  • positionMoveMode: Current physics mode ("spring" | "slide")
  • positionMoveConfigName: (Optional) Named physics config to use
  • positionMoveConfigs: (Optional) Custom physics configurations

4. Physics Parameters

{
  mass: number;        // Inertia (higher = slower response)
  stiffness: number;   // Spring force toward target (higher = tighter)
  damping: number;     // Resistance to oscillation
  stopSpeed: number;   // Threshold to stop animation
  friction: number;    // Slide mode: decay rate (0-1, higher = more friction)
}

Usage Examples

1D Mover: Rotate an Element

import { moverState, moverRefs, addMoverEffects } from "repond-movers";

const rotatingStore = {
  newState: () => ({
    ...moverState("rotation", { moveMode: "spring" }),
  }),
  newRefs: () => ({
    ...moverRefs("rotation", { stiffness: 20, damping: 8 }),
    element: null as HTMLElement | null,
  }),
};

addMoverEffects("rotating", "rotation", "1d");

// Animate rotation
setState("rotating", { rotationGoal: Math.PI }, "element1");

2D Mover: Drag & Drop with Momentum

const draggableStore = {
  newState: () => ({
    ...moverState("position", { moveMode: "slide" }),
    isDragging: false,
  }),
  newRefs: () => ({
    ...moverRefs("position", { friction: 0.15 }),
  }),
};

addMoverEffects("draggable", "position", "2d");

// On drag end: item slides with momentum
function onDragEnd(itemId: string, velocity: { x: number; y: number }) {
  setState("draggable", {
    positionMoveMode: "slide",
    isDragging: false
  }, itemId);
  // Velocity is maintained in refs, item slides to a stop
}

3D Mover: Character Movement

const characterStore = {
  newState: () => ({
    ...mover3dState("position", { moveMode: "spring" }),
  }),
  newRefs: () => ({
    ...mover3dRefs("position", { mass: 50, stiffness: 12, damping: 8 }),
    mesh: null as THREE.Mesh | null,
  }),
};

addMoverEffects("character", "position", "3d");

// Move character in 3D space
setState("character", {
  positionGoal: { x: 10, y: 0, z: -5 }
}, "hero");

Multi Mover: Character Blend Shapes

const characterStore = {
  newState: () => ({
    blendShapes: { smile: 0, blink: 0, speak: 0 },
    blendShapesGoal: { smile: 0, blink: 0, speak: 0 },
    blendShapesIsMoving: false,
    blendShapesMoveMode: "spring" as const,
  }),
  newRefs: () => ({
    ...moverMultiRefs("blendShapes", { stiffness: 25, damping: 10 }),
  }),
};

addMoverEffects("character", "blendShapes", "multi");

// Animate multiple blend shapes simultaneously
setState("character", {
  blendShapesGoal: { smile: 0.8, blink: 1.0, speak: 0.3 }
}, "hero");

Advanced Features

Multiple Physics Configs

Switch between different physics configurations dynamically:

const spriteStore = {
  newState: () => ({
    ...moverState("position", {
      moveMode: "spring",
      moveConfigName: "smooth" // Start with "smooth" config
    }),
  }),
  newRefs: () => ({
    ...moverRefs("position", {
      smooth: { mass: 50, stiffness: 12, damping: 9 },
      bouncy: { mass: 30, stiffness: 25, damping: 5 },
      snappy: { mass: 20, stiffness: 30, damping: 12 },
    }),
  }),
};

// Switch to bouncy physics mid-animation
setState("sprite", { positionMoveConfigName: "bouncy" }, "sprite1");

Time Control (Slow Motion, Pause)

Enable time control to slow down or pause all animations:

// 1. Setup with time tracking
initMovers(["game", "ticker", "elapsed"]);

// 2. Your game loop updates elapsed time each frame
const tickerStore = {
  newState: () => ({
    elapsed: 0, // Time in milliseconds
  }),
  newRefs: () => ({}),
};

// 3. Update elapsed time each frame
setState("ticker", { elapsed: Date.now() }, "game");

// 4. Slow down time (half speed)
const slowMotionFactor = 0.5;
setState("ticker", { elapsed: previousTime + (frameDelta * slowMotionFactor) }, "game");

// 5. Pause time (freeze animations)
// Simply stop updating the elapsed time state

Multiple Time Sources (Time Keys)

New in v1.2.0: Use different elapsed time states for different movers. Perfect for pausing game animations while keeping UI animations running, or controlling different animation layers independently.

// 1. Setup multiple named time sources
initMovers({
  default: ["app", "ticker", "elapsed"],    // Fallback
  game: ["game", "ticker", "elapsed"],      // Game world time
  ui: ["ui", "ticker", "elapsed"]           // UI time (always running)
});

// 2. Assign time keys to movers
addMoverEffects("character", "position", "3d", { timeKey: "game" });
addMoverEffects("button", "opacity", "1d", { timeKey: "ui" });
addMoverEffects("background", "scale", "2d"); // Uses "default"

// 3. Control time independently
function gameLoop() {
  const now = Date.now();

  // Always update UI time
  setState("ui", { elapsed: now }, "ticker");

  // Only update game time when not paused
  if (!isPaused) {
    setState("game", { elapsed: now }, "ticker");
  }

  // Result: Character animations freeze when paused, UI continues
}

Use Cases:

  • Pause gameplay while UI animations continue
  • Slow-motion effects on specific animation layers
  • Independent time control for cutscenes vs gameplay
  • Different physics simulation speeds (particles, background, etc.)

Backward Compatibility: Existing code using initMovers([...]) continues to work - the array format sets the "default" time key automatically.

Direct Mover Control

For advanced use cases, run movers directly without effects:

import { runMover } from "repond-movers";

// Manual control of a 2D mover
runMover("2d", {
  itemType: "sprite",
  itemId: "sprite1",
  name: "position",
  frameDuration: 16.667, // milliseconds (60fps)
});

API Reference

Initialization

// Single time source (backward compatible)
initMovers(timeElapsedStatePath?: [itemType, itemId, propertyName]);

// Multiple time sources (v1.2.0+)
initMovers(timeConfig: {
  default: [itemType, itemId, propertyName],
  [key: string]: [itemType, itemId, propertyName]
});

// Example:
initMovers({
  default: ["app", "ticker", "elapsed"],
  game: ["game", "ticker", "elapsed"],
  ui: ["ui", "ticker", "elapsed"]
});

Creating Movers

// 1D Mover
import { moverState, moverRefs } from "repond-movers";

moverState(name: string, initialState?: Partial<MoverState>)
moverRefs(name: string, config?: PhysicsConfig)

// 2D Mover
import { mover2dState, mover2dRefs } from "repond-movers";

mover2dState(name: string, initialState?: Partial<Mover2dState>)
mover2dRefs(name: string, config?: PhysicsConfig)

// 3D Mover
import { mover3dState, mover3dRefs } from "repond-movers";

mover3dState(name: string, initialState?: Partial<Mover3dState>)
mover3dRefs(name: string, config?: PhysicsConfig)

// Multi Mover
import { moverMultiRefs } from "repond-movers";

moverMultiRefs(name: string, config?: PhysicsConfig)

Adding Effects

addMoverEffects(
  itemType: string,
  moverName: string,
  moverType: "1d" | "2d" | "3d" | "multi",
  options?: {
    timeKey?: string  // Optional: specify which time source to use (v1.2.0+)
  }
)

// Example:
addMoverEffects("character", "position", "3d", { timeKey: "game" });

Running Movers Directly

runMover(
  moverType: "1d" | "2d" | "3d" | "multi",
  options: {
    itemType: string;
    itemId: string;
    name: string;
    frameDuration: number;
  }
)

How It Works

Frame-Rate Independence

Movers use fixed 0.5ms physics substeps internally, regardless of actual frame rate:

Frame (16.6ms at 60fps):
  ├─ Physics substep 0.5ms
  ├─ Physics substep 0.5ms
  ├─ ... (33 substeps)
  └─ Interpolate remaining time

This ensures smooth, consistent animations whether running at 30fps, 60fps, or 144fps.

Spring Physics

Spring mode uses spring-mass-damper system:

Force = -stiffness × distance - damping × velocity
Position += velocity × dt
Velocity += (Force / mass) × dt

Slide Physics

Slide mode uses exponential velocity decay:

velocity(t) = velocity₀ × e^(-k×t)
where k = -ln(1 - friction)

Stopping Detection

Movers track average speed over recent frames (default: 10 frames). Animation stops when:

  • Average speed < stopSpeed threshold
  • Value is near target

This prevents jitter and false stops during spring oscillation.


Performance

  • Time Complexity: O(1) per mover per frame (fixed substeps)
  • Space Complexity: O(1) per mover (fixed-size refs)
  • GC Pressure: Low (object pooling in 2D/3D movers)
  • Physics Fidelity: High (0.5ms substeps provide smooth curves)

Movers are designed for real-time applications with hundreds or thousands of animated entities.


TypeScript Support

Repond-movers inherits full type safety from Repond:

// Define your stores
declare module "repond/declarations" {
  interface CustomRepondTypes {
    ItemTypeDefs: {
      sprite: typeof spriteStore;
      character: typeof characterStore;
    };
  }
}

// Get full autocomplete
setState("sprite", { positionGoal: { x: 100, y: 50 } }); // ✓ Type-safe!

Tips & Best Practices

Choosing Physics Parameters

For smooth, natural movement:

{ mass: 50, stiffness: 12, damping: 9 }

For bouncy, playful animation:

{ mass: 30, stiffness: 25, damping: 5 }

For snappy, responsive movement:

{ mass: 20, stiffness: 30, damping: 12 }

For sliding with momentum:

{ friction: 0.15 } // Lower = slides further

When to Use Each Mover Type

  • 1D: Single values (rotation, scale, opacity, scroll position)
  • 2D: Screen-space positions, UI elements, 2D games
  • 3D: World-space positions, 3D cameras, 3D game objects
  • Multi: When you need to react to multiple related animations as a group

Avoiding Common Issues

Don't read state immediately after setting goal:

// ❌ Won't work - animation runs in effects
setState("sprite", { positionGoal: { x: 100 } });
console.log(getState("sprite").position); // Still at old position

// ✅ Use effects to respond to position changes
makeEffect((id) => {
  console.log(getState("sprite", id).position); // Updated!
}, { changes: ["sprite.position"] });

Switch modes smoothly:

// Velocity is preserved when changing modes/configs
setState("sprite", {
  positionMoveMode: "slide" // Switches from spring to slide, keeps velocity
});

Documentation


License

MIT


Acknowledgments

Built for real-time applications where smooth, realistic animations matter.

Depends on:

  • Repond - High-performance entity state management
  • Chootils - Utilities for point operations and physics