Popmotion

A functional, flexible JavaScript motion library

Popmotion is a 11kb Swiss Army knife for animators and UI developers:

Animations

Tween

Animate between two states with a scrubbable playhead. Includes a full suite of easing, and methods to generate your own.

tween({
  from: 0,
  to: { x: 300, rotate: 180 },
  duration: 1000,
  ease: easing.backOut,
  flip: Infinity
})
Fork on CodePen

Spring

An accurate spring simulation powered by the same equations as Apple’s CASpringAnimation.

Throw
spring({
  from: ballXY.get(),
  to: 0,
  velocity: ballXY.getVelocity(),
  stiffness: 200
})
Fork on CodePen

Keyframes

Animate through multiple sequential states with all the power of a single tween.

keyframes({
  values: [
    { x: 0, y: 0, rotateY: 0, background: '#9B65DE' },
    { x: 300, y: 0, rotateY: 180, rotateX: 0, background: '#14D790' },
    { x: 300, y: 200, rotateY: 180, rotateX: 180, background: '#FF1C68' },
    { x: 0, y: 200, rotateY: 0, rotateX: 180, background: '#198FE3' },
    { x: 0, y: 0, rotateY: 0, rotateX: 0, background: '#9B65DE' }
  ],
  duration: 3000,
  easings: easing.easeInOut,
  loop: Infinity
})
Fork on CodePen

Physics

A lightweight integrated physics simulation that can be altered mid-animation.

Tap
const ball = document.querySelector('.ball');
const ballY = value(0, styler(ball).set('y'));

const gravity = physics({
  acceleration: 2500,
  restSpeed: false
}).start(ballY);

listen(ball, 'mousedown touchstart').start(() => {
  gravity
    .set(ballY.get())
    .setVelocity(-1200);
});
Fork on CodePen

Decay

Smoothly dampen velocity for native-feeling momentum scrolling.

const slider = document.querySelector('.slider');
const sliderX = value(0, styler(slider).set('x'))

listen(slider, 'mousedown touchstart').start(() => {
  pointer({ x: sliderX.get() })
    .pipe(({ x }) => x, clampMovement)
    .start(sliderX);
});
  
listen(document, 'mouseup touchend').start(() => {
  decay({
    from: sliderX.get(),
    velocity: sliderX.getVelocity()
  }).pipe(clampMovement)
    .start(sliderX);
});
Fork on CodePen

Every Frame

Fires once every frame with the latest framestamp.

const ballStylers = Array
  .from(container.children)
  .map(styler);

const distance = 100;
const stagger = 0.5;
const speed = 0.004;

everyFrame()
  .start((timestamp) => ballStylers.forEach((style, i) => {
    const y = distance * Math.sin(speed * timestamp + (i * stagger));
    styler.set('y', y);
  }));
Fork on CodePen

Timeline

Script complicated sequences of tweens with a simple array.

timeline([
  { track: 'shade', from: 0, to: 1, ease: easing.linear },
  '-100',
  {
    track: 'modal',
    duration: 500,
    from: { y: -100, opacity: 0 },
    to: { y: 0, opacity: 1 },
    ease: { y: easing.backOut, opacity: easing.linear }
  }
])
Fork on CodePen

Input

Pointer

Provides absolute or relative pointer coordinates.

Drag
const ball = document.querySelector('.ball');
const ballXY = value({ x: 0, y: 0 }, styler(ball).set);

listen(ball, 'mousedown touchstart').start(() => {
  pointer(ballXY.get())
    .start(ballXY);
});
Fork on CodePen

Multitouch

Create multitouch gestures with a list of active touches, delta rotation and scale properties.

Pinch & zoom
const box = document.querySelector('.box');
const boxTransform = value({ x: 0, y: 0 }, styler(box).set);

listen(box, 'touchstart')
  .filter(({ touches }) => touches.length >= 2)
  .start(() => {
    multitouch(boxTransform.get())
      .start(boxTransform);
  });
Fork on CodePen

More features

Stagger

Stagger any series of animations or functions.

const stylers = Array
  .from(container.children)
  .map(styler); 

const animations = Array(stylers.length)
  .fill(spring({ to: 300 }));

stagger(animations, 100)
  .start((v) => v.forEach((x, i) => stylers[i].set('x', x)));

Color

Popmotion uses advanced linear RGB color blending to avoid muddy brightness loss.

keyframes({
  values: ['#14D790', '#198FE3', '#FF1C68', '#9B65DE', '#14D790'],
  duration: 10000,
  ease: easing.linear,
  loop: Infinity
})
Fork on CodePen

Crossfade

Blend the output of any two animations.

const blendedBall = styled(document.querySelector('.blended'));

const animateUp = tween({ from: -100, to: 100, flip: Infinity });
const animateDown = tween({ from: 100, to: -100, flip: Infinity });

crossfade(animateUp, animateDown)
  .start(blendedBall.set('y'));
Fork on CodePen

Line drawing

Animate SVG path elements to emulate drawing.

const circle = styler(document.querySelector('path.outline'));

tween({ ease: easing.easeIn }).start({
  update: (v) => circle.set({
    opacity: v,
    pathLength: v * 45
  }),
  complete: () => physics({ velocity: -400 })
    .start(circle.set('rotation'))
});
Fork on CodePen

Learn once, animate anything

Popmotion isn't opinionated, so you can pump out numbers to Three.js, React, A-Frame, PixiJS... anywhere!

const sphere = new THREE.Mesh(geometry, material);
const sphereY = value(5, (v) => sphere.position.y = v);

const gravity = physics({
  from: sphereY.get(),
  acceleration: - 9.8,
  restSpeed: false
}).pipe(
  (v) => (v <= 1) && gravity.setVelocity(10),
  (v) => Math.max(1, v)
).start(sphereY);
Fork on CodePen

Functional composition

Don't wait for the library authors to offer new features via configs. Use pure functions to compose your own.

Drag
const { transformMap, smooth } = transform;

const smoothXY = transformMap({
  x: smooth(200),
  y: smooth(200)
});

listen(ball, 'mousedown touchstart').start(() => 
  schedule(everyFrame(), pointer(ballXY.get()))
    .pipe(smoothXY)
    .start(ballXY)
);

Pick and choose any part of Popmotion by importing modules individually.

Or take it all for 11kb!