Canvas Curves

Quadratic and cubic Bézier curves let you draw smooth, organic lines by pulling a straight segment toward one or two control points.

Beyond Straight Lines

beginPath(), moveTo(), and lineTo() only ever produce straight edges. To draw smooth, flowing curves — arches, waves, leaf shapes, speech bubbles — Canvas provides two curve methods that both start from the current point set by a prior moveTo() or lineTo(): quadraticCurveTo() for simple curves with one control point, and bezierCurveTo() for more flexible curves with two.

Quadratic Curves with quadraticCurveTo()

quadraticCurveTo(cpx, cpy, x, y) draws a curve from the current point to (x, y), bending toward a single control point at (cpx, cpy). The curve never actually touches the control point — it only gets pulled toward it, the way a rubber band bends toward wherever you pinch it. Moving the control point further from the straight line between the start and end points produces a more pronounced bulge.

Example

<!DOCTYPE html>
<html>
<body>

<canvas id="scene" width="300" height="200" style="border:1px solid #ccc;"></canvas>

<script>
const ctx = document.getElementById('scene').getContext('2d');

ctx.beginPath();
ctx.moveTo(30, 120);
ctx.quadraticCurveTo(120, 20, 210, 120);

ctx.strokeStyle = '#0891b2';
ctx.lineWidth = 4;
ctx.stroke();
</script>

</body>
</html>

Cubic Bézier Curves with bezierCurveTo()

bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) works the same way but with two control points instead of one. The curve leaves the current point heading toward the first control point and arrives at (x, y) coming from the direction of the second control point, which makes it possible to draw S-shapes and other curves that a single control point cannot reach. Illustration and vector-graphics software use this same cubic Bézier model, so a curve designed in a tool like Figma or Illustrator maps directly onto bezierCurveTo() calls.

Note: Control points never appear in the final drawing — they only steer the curve. While designing a shape, it helps to temporarily plot each control point with a small filled circle so you can see exactly how it is pulling the line, then remove that debug code once the curve looks right.

Example

<!DOCTYPE html>
<html>
<body>

<canvas id="scene" width="300" height="220" style="border:1px solid #ccc;"></canvas>

<script>
const ctx = document.getElementById('scene').getContext('2d');

ctx.beginPath();
ctx.moveTo(30, 130);
ctx.bezierCurveTo(30, 30, 170, 200, 210, 60);

ctx.strokeStyle = '#db2777';
ctx.lineWidth = 4;
ctx.stroke();
</script>

</body>
</html>
  • quadraticCurveTo(cpx, cpy, x, y) uses one control point
  • bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) uses two control points
  • Both methods draw from the current point set by the prior moveTo() or lineTo()
  • Control points are never drawn themselves; they only shape the curve
  • Two control points make S-curves and complex bends possible; one control point cannot
MethodParameters
quadraticCurveTo()cpx, cpy, x, y — one control point, then the end point
bezierCurveTo()cp1x, cp1y, cp2x, cp2y, x, y — two control points, then the end point

Example

<!DOCTYPE html>
<html>
<body>

<canvas id="scene" width="300" height="200" style="border:1px solid #ccc;"></canvas>

<script>
const ctx = document.getElementById('scene').getContext('2d');
const x = 40, y = 30, w = 170, h = 100, r = 20;

ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.lineTo(x + w - r, y);
ctx.quadraticCurveTo(x + w, y, x + w, y + r);
ctx.lineTo(x + w, y + h - r);
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
ctx.lineTo(x + 60, y + h);
ctx.lineTo(x + 40, y + h + 30); // speech bubble tail
ctx.lineTo(x + 50, y + h);
ctx.lineTo(x + r, y + h);
ctx.quadraticCurveTo(x, y + h, x, y + h - r);
ctx.lineTo(x, y + r);
ctx.quadraticCurveTo(x, y, x + r, y);
ctx.closePath();

ctx.fillStyle = '#f8fafc';
ctx.fill();
ctx.strokeStyle = '#334155';
ctx.lineWidth = 3;
ctx.stroke();
</script>

</body>
</html>

Exercise: Canvas Curves

How many control points does quadraticCurveTo() use to shape its curve?