Skip to main content
Technology & EngineeringAnimation Services171 lines

Popmotion

Functional animation library — spring physics, decay, keyframe interpolation, and composable animation primitives that power Framer Motion's internals.

Quick Summary21 lines
You are an expert in Popmotion for building physics-based and functional animations in JavaScript.

## Key Points

- Prefer spring animations over duration-based tweens for interactive UI — springs respond naturally to interruption and re-targeting.
- Use `decay` for gesture-driven interactions (drag release, scroll fling) to preserve the user's intended momentum.
- Store animation controls and call `.stop()` before starting a new animation on the same property to avoid conflicting updates.
- Spring animations can oscillate indefinitely with very low damping. Use `restSpeed` and `restDelta` thresholds to ensure they settle: `animate({ ..., restSpeed: 0.5, restDelta: 0.5 })`.
- Popmotion outputs raw values, not CSS strings. Forgetting to append units (`px`, `deg`) when applying to DOM styles results in silent failures.

## Quick Example

```bash
npm install popmotion
```

```js
import { animate, spring, decay } from "popmotion";
```
skilldb get animation-services-skills/PopmotionFull skill: 171 lines
Paste into your CLAUDE.md or agent config

Popmotion — Web Animation

You are an expert in Popmotion for building physics-based and functional animations in JavaScript.

Core Philosophy

Overview

Popmotion is a low-level, functional animation library (~5KB) that provides composable animation primitives: animate, spring, decay, and keyframes. It is renderer-agnostic — it outputs numbers that you apply however you choose (DOM, Canvas, WebGL, React Native). Popmotion powers Framer Motion's internal animation engine, making it a solid choice when you need direct control without a framework-specific wrapper.

Setup & Configuration

npm install popmotion
import { animate, spring, decay } from "popmotion";

Popmotion works in any JavaScript environment — browser, Node, or worker threads. It has zero DOM dependencies.

Core Patterns

Basic Tween

import { animate } from "popmotion";

animate({
  from: 0,
  to: 100,
  duration: 500,
  onUpdate: (value) => {
    element.style.transform = `translateX(${value}px)`;
  },
  onComplete: () => console.log("done"),
});

Spring Animation

animate({
  from: 0,
  to: 200,
  type: "spring",
  stiffness: 200,
  damping: 20,
  mass: 1,
  onUpdate: (value) => {
    element.style.transform = `translateX(${value}px)`;
  },
});

Spring animations have no fixed duration — they resolve naturally based on physics parameters. This produces more realistic motion than easing curves.

Decay (Momentum)

animate({
  from: currentVelocity,
  velocity: 500,
  type: "decay",
  power: 0.8,
  timeConstant: 350,
  onUpdate: (value) => {
    element.style.transform = `translateX(${value}px)`;
  },
});

Decay is useful for flick/swipe gestures where you want the element to coast to a stop based on the release velocity.

Keyframes

animate({
  from: "#f00",
  to: "#00f",
  duration: 1000,
  onUpdate: (color) => {
    element.style.backgroundColor = color;
  },
});

// Multi-step keyframes
animate({
  to: [0, 100, 50, 150],
  duration: 2000,
  onUpdate: (value) => {
    element.style.transform = `translateY(${value}px)`;
  },
});

Stopping Animations

const controls = animate({
  from: 0,
  to: 500,
  duration: 1000,
  onUpdate: (v) => {
    element.style.transform = `translateX(${v}px)`;
  },
});

// Later — stops immediately, fires onComplete
controls.stop();

Composing Multiple Properties

function animateElement(el, target) {
  const xAnim = animate({
    from: parseFloat(el.dataset.x) || 0,
    to: target.x,
    type: "spring",
    stiffness: 300,
    damping: 25,
    onUpdate: (v) => { el.dataset.x = v; applyTransform(el); },
  });

  const yAnim = animate({
    from: parseFloat(el.dataset.y) || 0,
    to: target.y,
    type: "spring",
    stiffness: 300,
    damping: 25,
    onUpdate: (v) => { el.dataset.y = v; applyTransform(el); },
  });

  return { stop: () => { xAnim.stop(); yAnim.stop(); } };
}

function applyTransform(el) {
  el.style.transform = `translate(${el.dataset.x}px, ${el.dataset.y}px)`;
}

Best Practices

  • Prefer spring animations over duration-based tweens for interactive UI — springs respond naturally to interruption and re-targeting.
  • Use decay for gesture-driven interactions (drag release, scroll fling) to preserve the user's intended momentum.
  • Store animation controls and call .stop() before starting a new animation on the same property to avoid conflicting updates.

Common Pitfalls

  • Spring animations can oscillate indefinitely with very low damping. Use restSpeed and restDelta thresholds to ensure they settle: animate({ ..., restSpeed: 0.5, restDelta: 0.5 }).
  • Popmotion outputs raw values, not CSS strings. Forgetting to append units (px, deg) when applying to DOM styles results in silent failures.

Anti-Patterns

Using the service without understanding its pricing model. Cloud services bill differently — per request, per GB, per seat. Deploying without modeling expected costs leads to surprise invoices.

Hardcoding configuration instead of using environment variables. API keys, endpoints, and feature flags change between environments. Hardcoded values break deployments and leak secrets.

Ignoring the service's rate limits and quotas. Every external API has throughput limits. Failing to implement backoff, queuing, or caching results in dropped requests under load.

Treating the service as always available. External services go down. Without circuit breakers, fallbacks, or graceful degradation, a third-party outage becomes your outage.

Coupling your architecture to a single provider's API. Building directly against provider-specific interfaces makes migration painful. Wrap external services in thin adapter layers.

Install this skill directly: skilldb add animation-services-skills

Get CLI access →