Tweakit

Motion & curves

These controls edit plain config objects — a spring's physical parameters, a CSS easing curve, a function of x — that feed straight into your own animation code. All three are lazy-loaded heavy controls.

Spring

{ type: "spring" } is a stiffness / damping / mass tuner with a live settle-curve preview. The param is the plain { stiffness, damping, mass } object — here it drives a tiny integrator. Soften the damping, then send the ball.

const ball = target.querySelector(".spr-ball");
let x = 0, vel = 0, dest = 0, raf = 0;
const panel = tweaks("Spring", {
  motion: { type: "spring", stiffness: 220, damping: 18, mass: 1 },
  send: { type: "button", label: "Send it", action: () => { dest = dest ? 0 : 1; go(); } },
});
mount.append(panel.el);

const go = () => {
  cancelAnimationFrame(raf);
  const tick = () => {
    const { stiffness, damping, mass } = panel.params.motion;
    vel += ((-stiffness * (x - dest) - damping * vel) / mass) / 60;
    x += vel / 60;
    ball.style.left = `calc(8px + ${x} * (100% - 48px))`;
    if (Math.abs(vel) + Math.abs(x - dest) > 0.0005) raf = requestAnimationFrame(tick);
  };
  raf = requestAnimationFrame(tick);
};

mount is the panel's slot inside the stage; target is the demo surface it controls. In your own page you'd just document.body.append(panel.el).

Cubic bézier

A CSS easing editor — drag the two handles, or type into the X1/Y1/X2/Y2 fields. The param is the four-number array, ready for cubic-bezier(…) anywhere CSS takes a timing function. The dot below ping-pongs on an infinite animation; the curve is applied live.

const dot = target.querySelector(".bez-dot");
const panel = tweaks("Easing", {
  curve: { type: "cubicbezier", value: [0.25, 0.1, 0.25, 1] },
  seconds: [1.2, 0.2, 3, 0.1],
});
mount.append(panel.el);

const apply = (p) => {
  dot.style.animationTimingFunction = `cubic-bezier(${p.curve.join(", ")})`;
  dot.style.animationDuration = `${p.seconds}s`;
};
panel.on(apply);
panel.ready.then(() => apply(panel.params));

Plot

An expression grapher with a safe evaluator — no eval, just a small parser over x, the usual math functions and constants. The param is the expression string itself; type into the field to regraph. Pass fn instead to graph one of your own functions (read-only), and xMin/xMax/yMin/yMax/samples to frame it.

params.wave = "sin(x) * exp(-x / 6)"
const readout = target.querySelector(".plt-readout");
const panel = tweaks("Plot", {
  wave: { type: "plot", expr: "sin(x) * exp(-x / 6)", xMin: 0, xMax: 12 },
});
mount.append(panel.el);

const apply = (p) => {
  readout.textContent = `params.wave = ${JSON.stringify(p.wave)}`;
};
panel.on(apply);
panel.ready.then(() => apply(panel.params));