Canvas Lines

Lines on canvas are built by moving an invisible pen with moveTo(), drawing segments with lineTo(), and finally rendering the path with stroke().

Building a Path

Unlike fillRect, which draws a whole shape in one call, straight lines on canvas are built from a path: a series of points connected in order. You start a path, move an invisible pen to a starting coordinate, draw one or more straight segments from point to point, and then tell the context to actually render the accumulated path to the screen with stroke().

The Four Steps of Every Line

  • beginPath() - clears any previous path so old segments don't get redrawn.
  • moveTo(x, y) - moves the pen to a starting point without drawing anything.
  • lineTo(x, y) - adds a straight segment from the pen's current position to (x, y), and moves the pen there.
  • stroke() - paints all the segments added since beginPath() using the current strokeStyle and lineWidth.

Example

<!DOCTYPE html>
<html>
<body>

<canvas id="singleLine" width="300" height="150"></canvas>

<script>
  const ctx = document.getElementById("singleLine").getContext("2d");

  ctx.beginPath();
  ctx.moveTo(30, 100);
  ctx.lineTo(270, 30);
  ctx.strokeStyle = "darkslategray";
  ctx.lineWidth = 3;
  ctx.stroke();
</script>

</body>
</html>

Chaining Multiple Segments and Closing Paths

A single path can hold as many segments as you like - just keep calling lineTo() and each call adds a straight line from wherever the pen currently is to the new point. When you want the shape to return to its very first moveTo() point and form a closed polygon, call closePath() before stroke() (or fill()) instead of drawing the final segment yourself.

Note: If you forget beginPath() before starting a new line, your new moveTo()/lineTo() calls get appended to whatever path was already there, and calling stroke() re-renders every earlier segment too - a common source of ghost lines that won't go away.

Example

<!DOCTYPE html>
<html>
<body>

<canvas id="zigzag" width="320" height="120"></canvas>

<script>
  const ctx = document.getElementById("zigzag").getContext("2d");
  const points = [[20, 20], [80, 100], [140, 20], [200, 100], [260, 20], [300, 80]];

  ctx.beginPath();
  ctx.moveTo(points[0][0], points[0][1]);
  for (let i = 1; i < points.length; i++) {
    ctx.lineTo(points[i][0], points[i][1]);
  }
  ctx.strokeStyle = "mediumvioletred";
  ctx.lineWidth = 2;
  ctx.stroke();
</script>

</body>
</html>

Example

<!DOCTYPE html>
<html>
<body>

<canvas id="triangle" width="220" height="180"></canvas>

<script>
  const ctx = document.getElementById("triangle").getContext("2d");

  ctx.beginPath();
  ctx.moveTo(110, 20);
  ctx.lineTo(190, 160);
  ctx.lineTo(30, 160);
  ctx.closePath();

  ctx.lineWidth = 8;
  ctx.lineJoin = "round";
  ctx.strokeStyle = "darkorange";
  ctx.stroke();
</script>

</body>
</html>
Note: lineCap ("butt", "round", or "square") controls how the ends of open paths look, while lineJoin ("miter", "round", or "bevel") controls how corners between segments look. Both only become visible with a noticeably thick lineWidth.
Method/PropertyPurpose
beginPath()Starts a new, empty path
moveTo(x, y)Moves the pen without drawing
lineTo(x, y)Adds a straight segment to the path
closePath()Draws a segment back to the path's start
stroke()Renders the path's outline using strokeStyle
lineWidthThickness of the stroked line, in pixels

Exercise: Canvas Lines

Which property controls the thickness of a stroked line?