Bezier Curve
Bezier curves define smooth transitions between values. Theyβre the foundation of CSS easing functions and custom animation paths.
What is a Bezier Curve?
A cubic bezier curve is defined by 4 points: P0 (start), P1, P2, P3 (end).
P0 βββββββββββββββββββββββββββββ P3
\ /
\ P1 ββββββββββ P2 /
\ /
\____________________/
- P0 and P3 are fixed (0,0) and (1,1) in CSS
- P1 and P2 are control points you define
- The curve maps time (x-axis) to progress (y-axis)
CSS Cubic-Bezier
/* Built-in easings */
transition-timing-function: ease; /* cubic-bezier(0.25, 0.1, 0.25, 1) */
transition-timing-function: ease-in; /* cubic-bezier(0.42, 0, 1, 1) */
transition-timing-function: ease-out; /* cubic-bezier(0, 0, 0.58, 1) */
transition-timing-function: ease-in-out; /* cubic-bezier(0.42, 0, 0.58, 1) */
/* Custom */
transition-timing-function: cubic-bezier(0.68, -0.55, 0.27, 1.55);
Demo: Bezier Curve Visualizer
HTML
<style>
#bezier-canvas { border: 1px solid #e2e8f0; border-radius: 8px; display: block; margin: 0 auto; background: white; }
.bezier-controls { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }
.bezier-controls label { font-size: 0.875rem; color: #64748b; }
.bezier-controls input { width: 80px; padding: 4px 8px; border: 1px solid #cbd5e1; border-radius: 4px; }
</style>
<canvas id='bezier-canvas' width='300' height='300'></canvas>
<div class='bezier-controls'>
<label>P1: <input id='p1x' type='range' min='0' max='1' step='0.01' value='0.25'> <span id='p1x-val'>0.25</span>
<input id='p1y' type='range' min='0' max='2' step='0.01' value='0.1'> <span id='p1y-val'>0.10</span></label>
<label>P2: <input id='p2x' type='range' min='0' max='1' step='0.01' value='0.25'> <span id='p2x-val'>0.25</span>
<input id='p2y' type='range' min='-1' max='2' step='0.01' value='1'> <span id='p2y-val'>1.00</span></label>
</div>
<pre id='bezier-info' style='background:#f1f5f9;padding:12px;border-radius:6px;margin-top:8px;'></pre> JavaScript
const canvas = document.getElementById('bezier-canvas');
const ctx = canvas.getContext('2d');
const info = document.getElementById('bezier-info');
const p1x = document.getElementById('p1x');
const p1y = document.getElementById('p1y');
const p2x = document.getElementById('p2x');
const p2y = document.getElementById('p2y');
function draw() {
const w = canvas.width;
const h = canvas.height;
const pad = 40;
const size = w - pad * 2;
ctx.clearRect(0, 0, w, h);
// Grid
ctx.strokeStyle = '#e2e8f0';
ctx.lineWidth = 1;
for (let i = 0; i <= 4; i++) {
const pos = pad + (size / 4) * i;
ctx.beginPath(); ctx.moveTo(pos, pad); ctx.lineTo(pos, pad + size); ctx.stroke();
ctx.beginPath(); ctx.moveTo(pad, pos); ctx.lineTo(pad + size, pos); ctx.stroke();
}
// Helper labels
ctx.fillStyle = '#94a3b8';
ctx.font = '12px monospace';
ctx.fillText('0', pad - 10, pad + size + 15);
ctx.fillText('1', pad + size - 5, pad + size + 15);
ctx.fillText('0', pad - 20, pad + size + 4);
ctx.fillText('1', pad - 20, pad + 4);
// Scale
function toCanvas(x, y) {
return [pad + x * size, pad + size - y * size];
}
const [p0x, p0y] = toCanvas(0, 0);
const [p3x, p3y] = toCanvas(1, 1);
const [c1x, c1y] = toCanvas(parseFloat(p1x.value), parseFloat(p1y.value));
const [c2x, c2y] = toCanvas(parseFloat(p2x.value), parseFloat(p2y.value));
// Control lines
ctx.strokeStyle = '#94a3b8';
ctx.setLineDash([4, 4]);
ctx.beginPath(); ctx.moveTo(p0x, p0y); ctx.lineTo(c1x, c1y); ctx.stroke();
ctx.beginPath(); ctx.moveTo(p3x, p3y); ctx.lineTo(c2x, c2y); ctx.stroke();
ctx.setLineDash([]);
// Control points
ctx.fillStyle = '#6366f1';
ctx.beginPath(); ctx.arc(c1x, c1y, 5, 0, Math.PI * 2); ctx.fill();
ctx.beginPath(); ctx.arc(c2x, c2y, 5, 0, Math.PI * 2); ctx.fill();
// Bezier curve
ctx.strokeStyle = '#6366f1';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(p0x, p0y);
for (let t = 0; t <= 1; t += 0.01) {
const mt = 1 - t;
const x = mt * mt * mt * p0x + 3 * mt * mt * t * c1x + 3 * mt * t * t * c2x + t * t * t * p3x;
const y = mt * mt * mt * p0y + 3 * mt * mt * t * c1y + 3 * mt * t * t * c2y + t * t * t * p3y;
ctx.lineTo(x, y);
}
ctx.stroke();
// Endpoints
ctx.fillStyle = '#059669';
ctx.beginPath(); ctx.arc(p0x, p0y, 5, 0, Math.PI * 2); ctx.fill();
ctx.beginPath(); ctx.arc(p3x, p3y, 5, 0, Math.PI * 2); ctx.fill();
// Update info
const css = `cubic-bezier(${parseFloat(p1x.value).toFixed(3)}, ${parseFloat(p1y.value).toFixed(3)}, ${parseFloat(p2x.value).toFixed(3)}, ${parseFloat(p2y.value).toFixed(3)})`;
info.textContent = 'CSS: ' + css;
}
p1x.addEventListener('input', function() { document.getElementById('p1x-val').textContent = parseFloat(this.value).toFixed(2); draw(); });
p1y.addEventListener('input', function() { document.getElementById('p1y-val').textContent = parseFloat(this.value).toFixed(2); draw(); });
p2x.addEventListener('input', function() { document.getElementById('p2x-val').textContent = parseFloat(this.value).toFixed(2); draw(); });
p2y.addEventListener('input', function() { document.getElementById('p2y-val').textContent = parseFloat(this.value).toFixed(2); draw(); });
draw(); Live Output Window
The Math
The cubic bezier formula:
B(t) = (1-t)Β³Β·P0 + 3(1-t)Β²Β·tΒ·P1 + 3(1-t)Β·tΒ²Β·P2 + tΒ³Β·P3
Where t goes from 0 to 1, and P0-P3 are the control points.
Common CSS Easings
| Easing | cubic-bezier | Feel |
|---|---|---|
ease | (0.25, 0.1, 0.25, 1) | Subtle, natural |
linear | (0, 0, 1, 1) | Mechanical |
ease-in | (0.42, 0, 1, 1) | Slow start |
ease-out | (0, 0, 0.58, 1) | Slow end |
ease-in-out | (0.42, 0, 0.58, 1) | Slow start & end |
Overshoot Effects
By setting control points outside the 0-1 range, you create overshoot effects:
/* Bounce-like effect */
cubic-bezier(0.68, -0.55, 0.27, 1.55)
This makes the animation go slightly past the end and settle back β a βbouncyβ feel.
Bezier in JavaScript
function bezier(t, p1, p2) {
const mt = 1 - t;
// P0 = (0,0), P3 = (1,1)
const x = mt * mt * mt * 0 + 3 * mt * mt * t * p1.x + 3 * mt * t * t * p2.x + t * t * t * 1;
const y = mt * mt * mt * 0 + 3 * mt * mt * t * p1.y + 3 * mt * t * t * p2.y + t * t * t * 1;
return { x, y };
}
Key Takeaways
- Bezier curves map time to progress for animations
- CSS
cubic-bezier(p1x, p1y, p2x, p2y)defines custom easing - P0=(0,0) and P3=(1,1) are fixed
- Control points outside 0-1 create overshoot/bounce
- Built-in values:
ease,linear,ease-in,ease-out,ease-in-out - Use the visualizer above to design your own easings
- For complex multi-point curves, use SVG paths or JS animation libraries