Vawe

Motion

Springs, keyframe tables, the motion rules, and the moves to copy.

Motion in a page is plain web motion: CSS, Web Animations, or a window.seek(t) function. core/motion/springs.js adds pure math for it: functions of time, no state, no Math.random, no dependencies. core/motion/README.md is the reference.

import { spring, track, approach, kf, springLinear, springDuration, curveToLinear, CURVES, SPRINGS, rng, noise1 } from '../../core/motion/springs.js';

A spring as an easing

The renderer seeks document.getAnimations(), so a native animation needs no seek.

const { k, d } = SPRINGS.snappy;
el.animate([{ transform: 'translateY(40px)', opacity: 0 }, { transform: 'none', opacity: 1 }],
  { duration: springDuration(k, d) * 1000, easing: springLinear(k, d), fill: 'both', delay: 600 });

Do not fit a cubic-bezier() by hand. It starts too hard and stops too early. Use curveToLinear(fn) to turn any curve into an exact CSS linear() easing. CURVES holds expoOut, spring and overshoot.

Spring presets in SPRINGS: snappy for UI and leading edges, default, heavy for big type and logos, playful for visible overshoot.

A canvas in window.seek(t)

window.seek = (t) => {
  const x = track(t, [[0, 100], [1.2, 640], [2.4, 280]], 170, 26);  // one spring per target change
  const zoom = approach(t * 30, 1, 1.35, 0.15);                    // frame number, 15% of the gap per frame
  ctx.setTransform(zoom, 0, 0, zoom, 0, 0);
  ctx.fillRect(x, 200, 80, 80);
};

A [[f, v]] table with kf

Tunable numbers stay as literals so the studio can edit them. Values can be arrays, for colours.

const ROT = [[0, 0], [24, 90], [60, 90], [84, 0]];
const TINT = [[0, [20, 20, 30]], [60, [40, 90, 255]]];
const f = t * 30;
el.style.rotate = `${kf(f, ROT, 'easeInOutCubic')}deg`;
const [r, g, b] = kf(f, TINT);

Rules that are not your defaults

  • Arrive fast, land soft. Use approach(f, from, to, k) with k 0.12 to 0.19, not ease-out on everything.
  • Exits run faster and shorter than entrances.
  • Stagger siblings 30 to 80 ms. Never let a group land on one frame.
  • Blur follows motion. A moving thing may blur. A still thing never does.
  • Text holds long enough to read (rule readable-hold). Fix a fast read with a hold, never with a slower move.
  • The slowest beat is at least 3 times the fastest.
  • Cut on the beat, or two frames early.
  • Adjacent transitions change axis or direction.

The rules live in taste/rules/, one per file (readable-hold.md, speed-bands.md, no-tells.md). taste/build/CARD.md holds the rules the judge scores against and 5 anti-patterns.

Moves to copy

prompts/moves/README.md lists proven moves, grouped by job. Each move is a short CSS and Web Animations snippet with a one-second clip. Choose from the clip, copy the snippet, and keep its numbers. A few examples:

JobMoves
land a title, softtracking-collapse, mask-rise, blur-word-cascade
type a commandcaret-typing, caret-follow
change between shotscut-on-motion, whip-pan, zoom-through, color-block-wipe
move the cameracamera-moves, pull-back-reveal, parallax-dive, rack-focus
show a clickcursor-click, notification-pop
prove a numbercount-up, chart-build

The gallery is on the moves page. bin/vawe moves renders the clips again when a demo changes.

To draft only the seconds you work on, run bin/vawe dev <page> --from s --to s.

On this page