beginPath() empties the path. moveTo(x, y) starts a subpath, lineTo(x, y) draws a segment, and closePath() joins the end to the start with a proper corner.
<canvas id="c" style="width:600px; height:104px"></canvas><script src="hidpi.js"></script>
<script>
const ctx = hidpi(c);
ctx.lineWidth = 4; ctx.strokeStyle = '#1f5f8b'; ctx.beginPath();
const ratings = [4.6, 4.3, 4.8, 4.1, 4.5, 4.4]; // the six books, in order
ratings.forEach((r, i) => ctx.lineTo(20 + i * 50, 100 - (r - 4) * 100));
ctx.stroke();
ctx.beginPath(); ctx.moveTo(320, 95); ctx.lineTo(370, 10); ctx.lineTo(420, 95);
ctx.closePath(); ctx.fillStyle = '#e09a10'; ctx.fill(); ctx.stroke();
ctx.moveTo(460, 95); ctx.lineTo(580, 10); // no beginPath() here...
ctx.strokeStyle = '#b5452f'; ctx.stroke(); // ...so the triangle turns red too
</script>
A lineTo() with no current point acts as moveTo(), so the loop needs no first step. The last stroke repaints every subpath since beginPath(), turning the blue triangle outline red; in an animation loop the same slip grows the path every frame.
<!doctype html>
<style>
body { margin: 0; padding: 8px; font: 13px system-ui, sans-serif; background: #fafaf7; }
canvas { width: 100%; max-width: 600px; height: 150px; display: block; background: #fff; border: 1px solid #ddd; }
label { display: block; margin-top: 6px; }
</style>
<canvas id="c"></canvas>
<label><input type="checkbox" id="fix"> Call beginPath() before the last line</label>
<script>
// Size the bitmap to the CSS size times devicePixelRatio for sharp lines
function hidpi(c, r = devicePixelRatio) {
c.width = c.clientWidth * r; c.height = c.clientHeight * r;
const ctx = c.getContext('2d'); ctx.scale(r, r); return ctx;
}
const c = document.getElementById('c');
const ctx = hidpi(c);
const ratings = [4.6, 4.3, 4.8, 4.1, 4.5, 4.4]; // the six BookNest books
function draw(fixed) {
ctx.clearRect(0, 0, 600, 150);
ctx.lineWidth = 4; ctx.strokeStyle = '#1f5f8b';
// 1. A rating line: lineTo() with no current point acts as moveTo()
ctx.beginPath();
ratings.forEach((r, i) => ctx.lineTo(20 + i * 50, 130 - (r - 4) * 130));
ctx.stroke();
// 2. A closed triangle: closePath() joins the end to the start with a real corner
ctx.beginPath();
ctx.moveTo(320, 130); ctx.lineTo(370, 30); ctx.lineTo(420, 130);
ctx.closePath();
ctx.fillStyle = '#e09a10'; ctx.fill(); ctx.stroke();
// 3. A red line: without beginPath() the stroke repaints the triangle too
if (fixed) ctx.beginPath();
ctx.moveTo(460, 130); ctx.lineTo(580, 30);
ctx.strokeStyle = '#b5452f'; ctx.stroke();
ctx.font = '12px sans-serif'; ctx.fillStyle = '#263238';
ctx.fillText(fixed ? 'beginPath() first: only the line is red'
: 'no beginPath(): the triangle outline turns red too', 300, 146);
}
document.getElementById('fix').addEventListener('change', e => draw(e.target.checked));
draw(false);
</script>