vaweGitHub

MovesChange between shots

Speed ramp freeze

look: chrome1.5 sspeed-ramp-freeze.md

Speed ramp freeze: final frame of the transition animation
Tap the clip to play it.

When to use

Use when a beat needs weight before a change: motion slows to a near hold on the hit frame, then snaps on at more than full speed. It is a time remap, not an easing: story time is the integral of a speed curve, so everything that moves (the ball, its squash, the ring, the camera push) slows, stops and restarts together. The detail that sells it is the hit frame itself: at the deepest squash the blur is gone, the frame is sharp and slightly pushed in, and it is not dead (speed 0.03, not 0). Clip: speed-ramp-freeze.mp4. Demo: demo/speed-ramp-freeze.html.

The code

js
import '../../core/engine/page-api.js';
import { easeFn } from '../../core/motion/presets.js';
const land = easeFn('landSoft'), settle = easeFn('settle'), smooth = (x) => settle(Math.min(1, Math.max(0, x)));
const T = { ramp: 0.35, rampLen: 0.4, hold: 0.75, snap: 1.0 }, FREEZE = 0.03;   // seconds of real time
const speed = (t) => t < T.hold ? 1 - (1 - FREEZE) * smooth((t - T.ramp) / T.rampLen)
                  : t < T.snap ? FREEZE : 1 + 2.5 * Math.exp(-(t - T.snap) / 0.1);   // the snap: 3.5x, settling to 1x
const DT = 1 / 240, tau = [0];                                  // story time, integrated once, read by interpolation
for (let i = 1; i <= 480; i++) tau.push(tau[i - 1] + speed(i * DT) * DT);
vawe.onFrame((t) => {
  const p = tauAt(t);                                           // every position below is a function of p, never of t
  ball.style.transform = `translateY(${y(p)}px) scale(${1 + 0.32 * squash(p)}, ${1 - 0.36 * squash(p)})`;
  blur.setAttribute('stdDeviation', `0 ${Math.min(26, Math.abs(y(tauAt(t + 1 / 120)) - y(tauAt(t - 1 / 120))) / 2 * 0.3)}`);   // real-time speed
  world.style.transform = `scale(${1 + 0.06 * (1 - speed(t))})`;  // the push in follows the slow-down and drops at the snap
});

Easing and timing

The clip runs 1.5 s. The look is chrome.

The clip uses 2 named eases from core/motion/presets.js. Each is a pair of After Effects handles: influence is how far along the segment the handle reaches, and speed is how fast the value moves at the key, as a multiple of the segment's average speed. Motion in the docs explains both.

EaseLeaving a keyArriving at a key
landSoft20% influence, speed 2.575% influence, speed 0
settle60% influence, speed 060% influence, speed 0

Notes

Fall on y = -drop * (1 - (p / touch)^2) (gravity in story time), squash between the touch and the hit, rebound on EASE.landSoft of p - hit. The ramp is 0.4 s and the hold 0.25 s: a shorter hold reads as a stutter, a longer one as a dropped frame. The blur is measured in real time, so it vanishes in the hold and peaks (26 px) on the snap: that contrast is what the eye reads as speed. This is the slowest beat the film needs (at least 3x the fastest); use it once. Do not freeze on a frame that is mid-blur or has nothing to hit: the freeze needs a contact.

Looks (ground, type, radius, accent) live in demo/demo.css; this snippet keeps neutral tokens.

Sound

Sound: droplet at 0.75 s into the move, on the contact of the hit frame (default gain); nothing on the snap.