Problem
In my app I need to draw a lot of polygons fast, for this I am using the following canvas primitives
ctx.beginPath()
ctx.moveTo(points[0], points[1])
ctx.stroke()
Unfortunately when I try to style the width of each line that is being drawn, the lines extend beyond the points I moveTo. This is unexpected, I expected only the thickness of the line being changed, not their endpoints.
Example
I have distilled my problem to the following minimal case. I have created a blue bounding box that should contain the drawn triangle / polygon. This works correctly when the linewidth is set to 1, but as soon as I increase the line width, the polygon exceeds the expected bounding box.
const canvas = document.getElementById('canvas')
canvas.width = 256
canvas.height = 256
// Set a background
const ctx = canvas.getContext('2d')
ctx.fillStyle = "#EEEEEE";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
function drawGrid(ctx, spacing = 10, color = '#ccc') {
const w = ctx.canvas.width;
const h = ctx.canvas.height;
ctx.strokeStyle = color;
ctx.lineWidth = 1;
ctx.beginPath();
// Vertical lines
for (let x = 0; x <= w; x += spacing) {
ctx.moveTo(x + 0.5, 0+ 0.5);
ctx.lineTo(x+ 0.5, h+ 0.5);
}
// Horizontal lines
for (let y = 0; y <= h; y += spacing) {
ctx.moveTo(0+ 0.5, y+ 0.5);
ctx.lineTo(w+ 0.5, y+ 0.5);
}
ctx.stroke();
}
// Draw a 10x10 grid
drawGrid(ctx, 10, '#ddd');
// Draw expected bounding box with lineWidth 1, this is correct
ctx.strokeStyle = 'blue'
ctx.lineWidth = 1
ctx.strokeRect(100, 100, 12, 26)
// Draw a triangle / polygon that exceeds the bounding box when the lineWidth > 1
const line = [112, 112, 100, 100, 112, 124, 112, 112] // [x1, y1, x2, y2, etc.]
drawPolygon(line, 'red', 5) // Incorrect
// drawPolygon(line, 'orange', 2) // Incorrect
drawPolygon(line, 'green', 1) // Correct
function drawPolygon(points, color, lineWidth) {
ctx.strokeStyle = color
ctx.lineWidth = lineWidth
ctx.beginPath()
ctx.moveTo(points[0], points[1])
for (let i = 2; i < points.length; i += 2) {
ctx.lineTo(points[i], points[i + 1])
}
ctx.stroke()
}
<canvas id="canvas" style="transform: scale(2);"></canvas>
Possible cause
It looks like the lineWidth is added to the line end points. I cannot find anything on MDN that would suggest this would happen.
I would like a solution that increases the thickness of the lines, but keeps the polygon inside the bounding box. The solution must be fast as well.
Very much thanks to the knowledge of @C3roe there are 2 possible easy solutions.
The cause
Due to the default lineJoining method of the canvas miter the line extends beyond the set points.
Solutions
Setting the lineJoining method to something else, like the round option, the miter method is not used anymore and due to the different properties of this method it keeps near to the original points.
Setting the miterLimit to a low value, like 1px, prevents the miter method from extending far beyond the original points.
I like the round option best for aesthetic reasons, but the miterLimitmight be a more correct solution.
// Source - https://stackoverflow.com/q/79838941
// Posted by bart, modified by community. See post 'Timeline' for change history
// Retrieved 2025-12-05, License - CC BY-SA 4.0
const canvas = document.getElementById('canvas')
canvas.width = 256
canvas.height = 256
// Set a background
const ctx = canvas.getContext('2d')
ctx.fillStyle = "#EEEEEE";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
function drawGrid(ctx, spacing = 10, color = '#ccc') {
const w = ctx.canvas.width;
const h = ctx.canvas.height;
ctx.strokeStyle = color;
ctx.lineWidth = 1;
ctx.beginPath();
// Vertical lines
for (let x = 0; x <= w; x += spacing) {
ctx.moveTo(x + 0.5, 0+ 0.5);
ctx.lineTo(x+ 0.5, h+ 0.5);
}
// Horizontal lines
for (let y = 0; y <= h; y += spacing) {
ctx.moveTo(0+ 0.5, y+ 0.5);
ctx.lineTo(w+ 0.5, y+ 0.5);
}
ctx.stroke();
}
// Draw a 10x10 grid
drawGrid(ctx, 10, '#ddd');
// Draw expected bounding box with lineWidth 1, this is correct
ctx.strokeStyle = 'blue'
ctx.lineWidth = 1
ctx.strokeRect(100, 100, 12, 26)
// Draw a triangle / polygon that exceeds the bounding box when the lineWidth > 1
const line = [112, 112, 100, 100, 112, 124, 112, 112] // [x1, y1, x2, y2, etc.]
drawPolygon(line, 'red', 5) // Incorrect
// drawPolygon(line, 'orange', 2) // Incorrect
drawPolygon(line, 'green', 1) // Correct
function drawPolygon(points, color, lineWidth) {
ctx.strokeStyle = color
ctx.lineWidth = lineWidth
ctx.miterLimit = 1;
ctx.beginPath()
ctx.moveTo(points[0], points[1])
for (let i = 2; i < points.length; i += 2) {
ctx.lineTo(points[i], points[i + 1])
}
ctx.stroke()
}
<canvas id="canvas" style="transform: scale(2);"></canvas>