CSS Animation Playground — JavaScript source
Design and test CSS animations live - preview easing curves, durations, and keyframes, then copy the exact CSS.
This is the JavaScript implementation — the same logic the interactive tool runs, in a shareable, citable form.
// css-animation-playground - JavaScript port (CosmoDev polyglot showcase).
//
// CSS cubic-bezier easing utilities. Given an animation progress `x` in [0,1],
// solve the cubic-bezier easing curve for its output `y`, and round-trip
// control-point coords to/from the `cubic-bezier(x1, y1, x2, y2)` CSS string.
// No dependencies, fully deterministic, safe to run in the browser or in Node.
//
// Ported from src/lib/animation.ts - display source, part of CosmoDev's
// polyglot tool pages (dev.cosmolabs.org). Behavior is functionally equivalent
// to the canonical TypeScript implementation.
//
// The Bézier runs from P0=(0,0) to P3=(1,1) with control points
// P1=(x1,y1), P2=(x2,y2). Every function below is total: it never throws and
// always returns a finite value.
/* The four control-point coordinates as [x1, y1, x2, y2].
* @typedef {[number, number, number, number]} BezierCoords
*/
const BEZIER_RE =
/^\s*cubic-bezier\(\s*(-?\d*\.?\d+)\s*,\s*(-?\d*\.?\d+)\s*,\s*(-?\d*\.?\d+)\s*,\s*(-?\d*\.?\d+)\s*\)\s*$/i;
/**
* Polynomial coefficients for one axis (c1, c2 are the control-point coords on
* that axis). The cubic is rewritten in power form so it can be evaluated with
* nested multiplication.
*/
function bezierCoeffs(c1, c2) {
const c = 3 * c1;
const b = 3 * (c2 - c1) - c;
const a = 1 - c - b;
return { a, b, c };
}
/** Evaluate the axis polynomial: ((a·t + b)·t + c)·t (Horner form). */
function sample(t, coeffs) {
const { a, b, c } = coeffs;
return ((a * t + b) * t + c) * t;
}
/** Derivative of the axis polynomial: (3a·t + 2b)·t + c. */
function sampleDerivative(t, coeffs) {
const { a, b, c } = coeffs;
return (3 * a * t + 2 * b) * t + c;
}
/**
* Given an animation progress `x` in [0,1], solve the cubic-bezier easing for
* the corresponding output `y`. Uses Newton-Raphson (clamped). Endpoints are
* exact: y(0)=0 and y(1)=1. Never throws.
*/
export function cubicBezierY(x, x1, y1, x2, y2) {
// Total function: coerce non-finite inputs to 0, clamp progress, guarantee endpoints.
const fin = (v) => (Number.isFinite(v) ? v : 0);
const px = fin(x);
if (px <= 0) return 0;
if (px >= 1) return 1;
const xC = bezierCoeffs(fin(x1), fin(x2));
const yC = bezierCoeffs(fin(y1), fin(y2));
// Newton-Raphson: find t such that x(t) = px, then read y(t).
// px is a strong initial guess because x(t) is monotonic for valid curves.
let t = px;
for (let i = 0; i < 8; i++) {
const dx = sample(t, xC) - px;
if (Math.abs(dx) < 1e-6) break;
const d = sampleDerivative(t, xC);
if (Math.abs(d) < 1e-7) break; // guard against division by ~0
t -= dx / d;
}
if (t < 0) t = 0;
else if (t > 1) t = 1;
return sample(t, yC);
}
/** Format four control-point coords as a CSS `cubic-bezier(...)` string. */
export function cssBezier(x1, y1, x2, y2) {
// Round to the nearest 1e-6 and stringify. JS String() already renders
// integer-valued floats without a trailing ".0" and renders -0 as "0", so
// this matches the canonical TS output exactly.
const f = (n) => String(Math.round(n * 1e6) / 1e6);
return `cubic-bezier(${f(x1)},${f(y1)},${f(x2)},${f(y2)})`;
}
/**
* Parse a CSS `cubic-bezier(x1, y1, x2, y2)` string into its four coords.
* Returns null for anything that isn't a valid cubic-bezier() (including
* named easings like "linear"). Never throws.
*/
export function parseCssBezier(str) {
if (typeof str !== "string") return null;
const m = str.match(BEZIER_RE);
if (!m) return null;
const coords = [parseFloat(m[1]), parseFloat(m[2]), parseFloat(m[3]), parseFloat(m[4])];
if (coords.some((n) => !Number.isFinite(n))) return null;
return coords;
}
/** Named CSS easings expressed as their cubic-bezier control-point coords. */
export const EASING_PRESETS = {
linear: [0, 0, 1, 1],
ease: [0.25, 0.1, 0.25, 1],
"ease-in": [0.42, 0, 1, 1],
"ease-out": [0, 0, 0.58, 1],
"ease-in-out": [0.42, 0, 0.58, 1],
};
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →