<div class="wrap">
<svg class="graph" viewBox="-15 -70 130 185">
<path class="grid" d="M0,100 L100,100 M0,0 L0,100"/>
<path id="curve" class="curve" d=""/>
<line id="l1" class="cpline"/><line id="l2" class="cpline"/>
<circle id="cp1" class="cp" r="2.6"/><circle id="cp2" class="cp" r="2.6"/>
<circle id="tracer" class="tracer" r="3"/>
</svg>
<div class="track"><i id="ball" class="ball"></i></div>
<code class="label">cubic-bezier(.34, 1.56, .64, 1)</code>
</div>
.wrap{position:absolute;inset:8% 10%;display:grid;grid-template-rows:1fr auto auto;gap:12px}
.graph{display:block;width:100%;height:100%;min-height:0}
.grid{stroke:var(--line);stroke-width:1;fill:none}
.curve{stroke:var(--accent);stroke-width:2.5;fill:none}
.cpline{stroke:var(--muted);stroke-width:.8;stroke-dasharray:2 2}
.cp{fill:var(--accent-2)}
.tracer{fill:var(--accent-3)}
.track{position:relative;height:10px;border-radius:6px;background:var(--line)}
.ball{position:absolute;top:50%;left:0;width:16px;height:16px;margin-top:-8px;border-radius:50%;background:var(--accent-3);box-shadow:0 2px 6px rgba(0,0,0,.25)}
.label{font:11px/1 ui-monospace,monospace;color:var(--muted);text-align:center}
const P1 = [.34, 1.56], P2 = [.64, 1];
const curve = document.getElementById('curve');
const cp1 = document.getElementById('cp1'), cp2 = document.getElementById('cp2');
const l1 = document.getElementById('l1'), l2 = document.getElementById('l2');
const tracer = document.getElementById('tracer');
const ball = document.getElementById('ball');
function toSvg(x, y) { return [x * 100, 100 - y * 100]; }
const p1s = toSvg(P1[0], P1[1]), p2s = toSvg(P2[0], P2[1]);
curve.setAttribute('d', 'M0,100 C' + p1s[0] + ',' + p1s[1] + ' ' + p2s[0] + ',' + p2s[1] + ' 100,0');
cp1.setAttribute('cx', p1s[0]); cp1.setAttribute('cy', p1s[1]);
cp2.setAttribute('cx', p2s[0]); cp2.setAttribute('cy', p2s[1]);
l1.setAttribute('x1', 0); l1.setAttribute('y1', 100); l1.setAttribute('x2', p1s[0]); l1.setAttribute('y2', p1s[1]);
l2.setAttribute('x1', 100); l2.setAttribute('y1', 0); l2.setAttribute('x2', p2s[0]); l2.setAttribute('y2', p2s[1]);
function bezier(t) {
const x = 3 * (1 - t) ** 2 * t * P1[0] + 3 * (1 - t) * t ** 2 * P2[0] + t ** 3;
const y = 3 * (1 - t) ** 2 * t * P1[1] + 3 * (1 - t) * t ** 2 * P2[1] + t ** 3;
return [x, y];
}
const start = performance.now();
function loop(now) {
const dur = 2200;
const t = ((now - start) % dur) / dur;
const pt = bezier(t);
const s = toSvg(pt[0], pt[1]);
tracer.setAttribute('cx', s[0]); tracer.setAttribute('cy', s[1]);
const p = Math.max(0, Math.min(1, pt[1]));
ball.style.left = 'calc(' + (p * 100) + '% - ' + (p * 16) + 'px)';
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);