Tweakit v0.7.2

Motion & curves

These controls edit plain values that feed straight into your own animation code: a transition's timing, a spring's parameters, a CSS easing curve, a function of x. All of them are lazy-loaded heavy controls.

Motion

{ type: "motion" } is one control for a transition's timing: an easing curve with a duration, or a spring tuned by feel (duration + bounce), switched inside the popover over one value. Either way the param carries the CSS it resolves to, duration in ms and an easing string (cubic-bezier(…), or a sampled linear(…) for the spring), so one transition works in both modes. The spring's fields read in ms and %, but the value keeps visualDuration in seconds and bounce as −1 to 1, as the snippet writes them; a stiffness/damping/mass config is accepted and mapped to the nearest pair. Copy and Paste at the foot of the popover move a whole motion to another transition, switching it to easing or spring as the copy is. Tap anywhere on the stage and the box goes there with the motion; make the spring bouncier, or switch to an easing.

const stage = target.querySelector(".mo-stage"), box = target.querySelector(".mo-box");
const panel = tweaks("Motion", {
  move: { type: "motion", visualDuration: 0.45, bounce: 0.3 },   // or { type: "motion", curve: "ease-out", duration: 240 }
});
mount.append(panel.el);

const apply = (p) => { box.style.transition = `transform ${p.move.duration}ms ${p.move.easing}`; };
panel.on(apply);
panel.ready.then(() => apply(panel.params));
stage.addEventListener("pointerdown", (e) => {
  const r = stage.getBoundingClientRect();
  const x = Math.max(0, Math.min(r.width - 40, e.clientX - r.left - 20)); // the box's left edge, kept inside the stage
  box.style.transform = `translateX(${x}px)`;
});

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).

Spring and bézier on their own

When the motion isn't a transition, use the editors inside Motion alone. { type: "spring" } has two modes, physics (stiffness / damping / mass) and time (duration + bounce), and resolves to { stiffness, damping, mass } either way (plus the time pair when tuned by feel) for a simulation that reads them every frame (the ball here is a tiny integrator). { type: "cubicbezier" } is the bare four-number curve for anything that takes a timing function on its own (the dot's keyframe animation).

const ball = target.querySelector(".pc-ball"), dot = target.querySelector(".pc-dot");
let x = 0, vel = 0, dest = 0, raf = 0;
const panel = tweaks("Pieces", {
  spring: { type: "spring", stiffness: 220, damping: 18, mass: 1 },
  send: { type: "button", label: "Send the ball", action: () => { dest = dest ? 0 : 1; go(); } },
  curve: { type: "cubicbezier", value: [0.25, 0.1, 0.25, 1] },
  seconds: { type: "slider", value: 1.2, min: 0.2, max: 3, step: 0.1, unit: "s" },
});
mount.append(panel.el);

const go = () => {
  cancelAnimationFrame(raf);
  const tick = () => {
    const { stiffness, damping, mass } = panel.params.spring;
    vel += ((-stiffness * (x - dest) - damping * vel) / mass) / 60;
    x += vel / 60;
    ball.style.left = `calc(8px + ${x} * (100% - 46px))`;
    if (Math.abs(vel) + Math.abs(x - dest) > 0.0005) raf = requestAnimationFrame(tick);
  };
  raf = requestAnimationFrame(tick);
};
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, and calculator shorthand such as 2x and sin x. 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), or editable: false to drop the field and keep a fixed expression, and xMin/xMax/yMin/yMax to frame it and samples for the resolution.

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));