Why do shapes inside canvas have blurred outlines?

To be on point, it just could be that you have set your canvas dimensions through CSS. You must not do that like I did once! Set width and height through default attributes that come with <canvas>.

We’ll do a little coding to look at the difference between these two approaches.

Start off with the following HTML:

<div id="box-canvas-css">
  <p>Canvas with width and height set with CSS (Inner borders are blurred)</p>
  <canvas id="canvas-css"></canvas>
</div>

<div id="box-canvas-attr">
  <p>Canvas with width and height set with default attributes (Inner borders are sharp)</p>
  <canvas id="canvas-attr"></canvas>
</div>

And CSS:

body{
  margin: 0;
  width: 100vw;
  height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: space-around;
  align-items: center
}
#box-canvas-css, #box-canvas-attr{
  width: 90%;
  height: 10%;
}

Place your <script> after the HTML and first pick up the two canvases:

let canvasCSS = document.getElementById('canvas-css');
let canvasATTR = document.getElementById('canvas-attr');

Set width and height through CSS for canvasCSS:

canvasCSS.style.width = document.getElementById('box-canvas-css').offsetWidth;
canvasCSS.style.height = document.getElementById('box-canvas-css').offsetHeight;

And for canvasATTR, set them through default attributes:

canvasATTR.width = document.getElementById('box-canvas-attr').offsetWidth;
canvasATTR.height = document.getElementById('box-canvas-attr').offsetHeight;

Now we can draw the shapes:

We will draw four rectangles lined up horizontally on each canvas. colors below represent the four colors for each of them.

let colors = ['rgb(136, 0, 68)', 'rgb(230, 0, 115)', 'rgb(255, 91, 173)', 'rgb(255, 183, 219)'];

We can whip up a function to do the drawing in each canvas. First it calls getContext(), then inside the loop which iterates four times, it picks up one color at a time from colors, and draws four rectangles placed one next to the other.

function draw(canvas, colors){
  let context = canvas.getContext('2d');

  for(let i=0; i < 4; i++){
    context.fillStyle = colors[i];
    context.fillRect((i*.25*canvas.width), 0, (.25*canvas.width), canvas.height);
  }
}

Now we can call it:

draw(canvasCSS, colors);
draw(canvasATTR, colors);

Look how the rectangles in the first canvas has visibly blurred inner borders, while in the second one they are as sharp as they should be. The CSS just gives a stretched out look to the canvas instead of setting the dimensions right.

demo. shows the difference between setting canvas dimensions through css and default attributes

You can find the complete code with a demo here on GitHub.