94

I have a <div style="border:1px solid border;" /> and canvas, which is drawn using:

context.lineWidth = 1;
context.strokeStyle = "gray";

The drawing looks quite blurry (lineWidth less than one creates even worse picture), and nothing near to the div's border. Is it possible to get the same quality of drawing as HTML using canvas?

var ctx = document.getElementById("canvas").getContext("2d");
ctx.lineWidth = 1;
ctx.moveTo(2, 2);
ctx.lineTo(98, 2);
ctx.lineTo(98, 98);
ctx.lineTo(2, 98);
ctx.lineTo(2, 2);
ctx.stroke();
div {
  border: 1px solid black;
  width: 100px;
  height: 100px;
}
canvas, div {background-color: #F5F5F5;}
canvas {border: 1px solid white;display: block;}
<table>
<tr><td>Line on canvas:</td><td>1px border:</td></tr>
<tr><td><canvas id="canvas" width="100" height="100"/></td><td><div>&nbsp;</div></td></tr>
</table>
Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778

16 Answers16

113

I found that setting the canvas size in CSS caused my images to be displayed in a blurry manner.

Try this:

<canvas id="preview" width="640" height="260"></canvas>

as per my post: HTML Blurry Canvas Images

Community
  • 1
  • 1
Ben Pretorius
  • 4,079
  • 4
  • 34
  • 28
  • 1
    Adding to this, you should ideally set the width and height attributes despite setting these properties via CSS. – Rutwick Gangurde Sep 06 '13 at 14:43
  • 9
    What an odd thing. *This, this is why you keep scrolling.* – Casey Dwayne Sep 02 '15 at 07:38
  • 1
    What is the workaround if I am forced to set height/width of the canvas using CSS/Javascript? For example in my case I need the canvas to be responsive, so I need to calculate it's dimensions on the fly and set it. Any ideas? Thanks. – Souvik Ghosh Sep 15 '16 at 16:53
  • You can hook into the javascript resize event handlers and just redraw the canvas each time the size handler is fired:-) – Ben Pretorius Sep 16 '16 at 06:03
  • 1
    This resolved my issue, finally!! Also according to my testing, setting the CSS canvas size via DOM does NOT make things blurry, so if anyone wants they can try setting the css by doing something like `canvas.style.width = '200px'`, it worked for me – Imtinan Azhar Jan 20 '21 at 20:49
  • You saved me a lot of time. Thanks! This should be marked as accepted answer. – ahmethungari Sep 23 '22 at 19:23
74

When drawing lines in canvas, you actually need to straddle the pixels. It was a bizarre choice in the API in my opinion, but easy to work with:

Instead of this:

context.moveTo(10, 0);
context.lineTo(10, 30);

Do this:

context.moveTo(10.5, 0);
context.lineTo(10.5, 30);

Dive into HTML5's canvas chapter talks about this nicely

Martin Tournoij
  • 26,737
  • 24
  • 105
  • 146
Matt Greer
  • 60,826
  • 17
  • 123
  • 123
  • Don't the Y coordinates also need to be offset by 1/2 a pixel? – user1118321 Jan 02 '12 at 01:14
  • 1
    Yeah, they most likely should too. In Dive into HTMl5 he didn't bother, because he was drawing lines clear across the whole canvas, so I just copied what he has to make reading my answer then reading his explanation more consistent. – Matt Greer Jan 02 '12 at 16:57
  • 17
    Thank you, this helped me fix a very annoying issue. I did it slightly differently I did `context.translate(0.5, 0.5);` before drawing and then `context.translate(-0.5, -0.5);` afterwards so I didn't have to add 0.5 to everything – Chris May 16 '18 at 11:31
  • 2
    This is going to sound obvious but it's an easy mistake to make: be sure your browser view is zoomed to 100% (Cmd-0). I had mine zoomed in and couldn't figure out for 10 minutes why my lines wouldn't get sharp. Duh. – Juniper Jones Feb 28 '19 at 14:12
  • 1
    Note: it's also how OpenGL/DirectX/Metal/Vulkan work. If you work through the math it ends up making sense why it's the way it is. The values passed to canvas are the edges of pixels. If you say `fillRect(0,0,1,1)` you're saying fill the rectangle that starts at the top left corner of the pixel at 0,0 and ends at the bottom right corner of the pixel at 0,0. Add in the current transform and you can probably see why it's designed this way. You're not drawing pixels, you're drawing abitrary rectangles. Lines are no different, they are just an expanded rectangle around the line. – gman Jan 09 '20 at 16:26
  • Please refer to the answer below about not setting height/width of canvas via css for a better solution IMO. – Fahad Jul 28 '20 at 15:43
59

Even easier fix is to just use this:

context = canvas.context2d;    
context.translate(0.5, 0.5);

From here on out your coordinates should be adjusted by that 0.5 pixel.

revgum
  • 1,147
  • 8
  • 22
  • 1
    @semicolon you need to translate the canvas perpendicular to the direction the line you're drawing is going. You don't need to translate fillRect unless you're drawing them on a x.5 pixel. – Super Fighting Robot Jun 26 '15 at 20:52
  • 1
    Just to note, this translation happens each time it's called. It's not relative to 0 but rather the values are added to x and y each time. I had a bug that meant my canvas was shifting diagonally and this was the cause. – brad May 06 '20 at 18:05
34

I use a retina display and I found a solution that worked for me here.

Small recap :

First you need to set the size of your canvas twice as large as you want it, for example :

canvas = document.getElementById('myCanvas');
canvas.width = 200;
canvas.height = 200;

Then using CSS you set it to the desired size :

canvas.style.width = "100px";
canvas.style.height = "100px";

And finally you scale the drawing context by 2 :

const dpi = window.devicePixelRatio;
canvas.getContext('2d').scale(dpi, dpi);
Stephen Paul
  • 37,253
  • 15
  • 92
  • 74
LittleJoe
  • 441
  • 4
  • 4
  • 16
    This is basically right, but better is to use `window.devicePixelRatio`: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio – langpavel Aug 07 '18 at 02:39
  • 1
    This should be the right answer, just if @LittleJoe could update the answer as `langpavel` mentioned by adding `window.devicePixelRatio` as the scaling ratio. – parse Aug 08 '19 at 17:24
  • 1
    this solution with `window.devicePixelRatio`fixed it for me. Its perfectly sharp now on my device. thanks @langpavel and @littlejoe – Lukas R. Mar 29 '20 at 17:42
  • how would you plug `window.devicePixelRatio` into this? Something like this `var dpr = window.devicePixelRatio; canvas.getContext('2d').scale(dpr,dpr);`? And if that is the case would you also still be doubling your canvas size then halving it in css? I'm trying to draw a perfect ruler by using `window.devicePixelRatio` but I have to recalculate everything accordingly if use it in `canvas.getContext('2d').scale()` – JohnRDOrazio May 01 '20 at 17:30
  • It seems I can get pretty close if instead of doubling the original size I multiply it by the result of `window.devicePixelRatio`, then put my intended size in css, then scale by `window.devicePixelRatio` as in my previous comment. However the value of `window.devicePixelRatio` is obviously only available in javascript not in html, so that's not much good when it comes to creating a universal solution... – JohnRDOrazio May 01 '20 at 17:36
29

The Mozilla website has example code for how to apply the correct resolution in a canvas: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio

enter image description here

var canvas = document.getElementById('canvas');

var ctx = canvas.getContext('2d');

// Set display size (css pixels).
var size = 200;
canvas.style.width = size + "px";
canvas.style.height = size + "px";

// Set actual size in memory (scaled to account for extra pixel density).
var scale = window.devicePixelRatio; // Change to 1 on retina screens to see blurry canvas.
canvas.width = size * scale;
canvas.height = size * scale;

// Normalize coordinate system to use css pixels.
ctx.scale(scale, scale);

ctx.fillStyle = "#bada55";
ctx.fillRect(10, 10, 300, 300);
ctx.fillStyle = "#ffffff";
ctx.font = '18px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';

var x = size / 2;
var y = size / 2;

var textString = "I love MDN";
ctx.fillText(textString, x, y);
<canvas id="canvas"></canvas>
mfluehr
  • 2,832
  • 2
  • 23
  • 31
Nic Scozzaro
  • 6,651
  • 3
  • 42
  • 46
15

Lines are blurred because the canvas virtual size is zoomed to its HTML element actual size. To overcome this issue you need to adjust canvas virtual size before drawing:

function Draw () {
 var e, surface;
 e = document.getElementById ("surface");
 /* Begin size adjusting. */
 e.width = e.offsetWidth;
 e.height = e.offsetHeight;
 /* End size adjusting. */
 surface = e.getContext ("2d");
 surface.strokeRect (10, 10, 20, 20);
}
window.onload = Draw ()
<!DOCTYPE html>
<html>
<head>
<title>Canvas size adjusting demo</title>
</head>
<body>
<canvas id="surface"></canvas>
</body>
</html>

HTML:

R0bur
  • 315
  • 2
  • 6
6

Ok, I've figured this out once and for all. You need to do two things:

  1. place any lines on 0.5 px. Refer to this, which provides a great explanation:

https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Applying_styles_and_colors#A_lineWidth_example

  1. There are essentially two heights and two widths associated with the canvas. There is the canvas height and width and then there is the css style height and width of the element. These need to be in sync.

To do this, you need to calculate the css height and width as:

 var myCanvasEl = document.getElementById('myCanvas');
 var ctx = myCanvasEl.getContext('2d');
 myCanvasEl.style.height = myCanvasEl.height / window.devicePixelRatio + "px";
 myCanvasEl.style.width = myCanvasEl.width / window.devicePixelRatio + "px";
 

where myCanvasEl.style.height and myCanvasEl.style.widthis the css styling height and width of the element, while myCanvasEl.height and myCanvasEl.width is the height and width of the canvas.

OLD ANSWER (superseded by above):

This is the best solution I've found in 2020. Notice I've multiplied the devicePixelRatio by 2:

 var size = 100;
 var scale = window.devicePixelRatio*2;
 context.width = size * scale;
 cartesian_001El.style.height = cartesian_001El.height / window.devicePixelRatio + "px";
 cartesian_001El.style.height = cartesian_001El.height / window.devicePixelRatio + "px";
 context.height = size * scale;
 context.scale(scale, scale);
Normajean
  • 1,075
  • 3
  • 11
  • 28
3

Something else that nobody talked about here when images are scaled (which was my issue) is imageSmoothingEnabled.

The imageSmoothingEnabled property of the CanvasRenderingContext2D interface, part of the Canvas API, determines whether scaled images are smoothed (true, default) or not (false). On getting the imageSmoothingEnabled property, the last value it was set to is returned.

This property is useful for games and other apps that use pixel art. When enlarging images, the default resizing algorithm will blur the pixels. Set this property to false to retain the pixels' sharpness.

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/imageSmoothingEnabled

To disable it, simply set the properity to false:

ctx.imageSmoothingEnabled = false;
Fabien Sa
  • 9,135
  • 4
  • 37
  • 44
2
canvas.width=canvas.clientWidth
canvas.height=canvas.clientHeight
Nick Vu
  • 14,512
  • 4
  • 21
  • 31
Igor Lee
  • 41
  • 3
1

To avoid this issue in animation I would like to share a small demo.

Basically I am checking increment values each time & jumping in a set of 1px by removing float values.

HTML:

<canvas id="canvas" width="600" height="600"></canvas>

CSS:

  html, body{
    height: 100%;
  }
  body{
    font-family: monaco, Consolas,"Lucida Console", monospace;
    background: #000;
  }

  canvas{
    position: fixed;
    top: 0;
    left: 0;
    transform: translateZ(0);
  }

JS:

  canvas = document.getElementById('canvas');
  ctx = canvas.getContext('2d');

  ctx.translate(0.5, 0.5);

  var i = 0;
  var iInc = 0.005;
  var range = 0.5;

  raf = window.requestAnimationFrame(draw);

  function draw() {
    var animInc = EasingFunctions.easeInQuad(i) * 250;
    ctx.clearRect(0, 0, 600, 600);
    ctx.save();
    ctx.beginPath();
    ctx.strokeStyle = '#fff';
    var rectInc = 10 + animInc;

    // Avoid Half Pixel
    rectIncFloat = rectInc % 1; // Getting decimal value.
    rectInc = rectInc - rectIncFloat; // Removing decimal.

    // console.log(rectInc);
    ctx.rect(rectInc, rectInc, 130, 60);
    ctx.stroke();
    ctx.closePath();

    ctx.font = "14px arial";
    ctx.fillStyle = '#fff';
    ctx.textAlign = 'center';
    ctx.fillText("MAIN BUTTON", 65.5 + rectInc, 35.5 + rectInc);

    i += iInc;

    if (i >= 1) {
      iInc = -iInc;
    }
    if (i <= 0) {
      iInc = Math.abs(iInc);
    }

    raf = window.requestAnimationFrame(draw);
  }


  // Easing
  EasingFunctions = {
    // no easing, no acceleration
    linear: function(t) {
      return t
    },
    // accelerating from zero velocity
    easeInQuad: function(t) {
      return t * t
    },
    // decelerating to zero velocity
    easeOutQuad: function(t) {
      return t * (2 - t)
    },
    // acceleration until halfway, then deceleration
    easeInOutQuad: function(t) {
      return t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t
    },
    // accelerating from zero velocity 
    easeInCubic: function(t) {
      return t * t * t
    },
    // decelerating to zero velocity 
    easeOutCubic: function(t) {
      return (--t) * t * t + 1
    },
    // acceleration until halfway, then deceleration 
    easeInOutCubic: function(t) {
      return t < .5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
    },
    // accelerating from zero velocity 
    easeInQuart: function(t) {
      return t * t * t * t
    },
    // decelerating to zero velocity 
    easeOutQuart: function(t) {
      return 1 - (--t) * t * t * t
    },
    // acceleration until halfway, then deceleration
    easeInOutQuart: function(t) {
      return t < .5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t
    },
    // accelerating from zero velocity
    easeInQuint: function(t) {
      return t * t * t * t * t
    },
    // decelerating to zero velocity
    easeOutQuint: function(t) {
      return 1 + (--t) * t * t * t * t
    },
    // acceleration until halfway, then deceleration 
    easeInOutQuint: function(t) {
      return t < .5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t
    }
  }
Imran Bughio
  • 4,811
  • 2
  • 30
  • 53
1

A related issue could be that you're setting the <canvas>'s height and width from CSS or other sources. I'm guessing it scales the canvas and associated drawings. Setting the <canvas> size using the height and width property (either from the HTML tag or a JS script) resolved the error for me.

krishnakeshan
  • 1,242
  • 2
  • 14
  • 18
1

Here is my solution: set width and height for canvas

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

Also set in css, so it will not overflow from its parent

canvas {
  width: 100%
  height: 100%
}
Tạ Anh Tú
  • 141
  • 1
  • 4
1

Although LittleJoe's solution worked perfect on desktop it didn't work on mobile because on iphone 11 pro for example the dpi is 3 so I had to set width/height based on dpi. At the end it worked:

let width = 100, height = 100;
const dpi = window.devicePixelRatio;

canvas = document.getElementById('myCanvas');
canvas.width = width * dpi;
canvas.height = height * dpi;

canvas.style.width = width + "px";
canvas.style.height = width + "px";

canvas.getContext('2d').scale(dpi, dpi);
Nika Tsogiaidze
  • 949
  • 14
  • 18
0

in order to get rid of the blurryness you need to set the size of the canvas in two manners: first withcanvas.width = yourwidthhere; and canvas.height = yourheighthere; second by setting the css attribute either by js or a stylesheet

0

HTML:

<canvas class="canvas_hangman"></canvas>

JS:

function setUpCanvas() {
    canvas = document.getElementsByClassName("canvas_hangman")[0];
    ctx = canvas.getContext('2d');
    ctx.translate(0.5, 0.5);
    
    // Set display size (vw/vh).
    var sizeWidth = 80 * window.innerWidth / 100,
        sizeHeight = 100 * window.innerHeight / 100 || 766; 

    // console.log(sizeWidth, sizeHeight);
    // Setting the canvas height and width to be responsive
    canvas.width = sizeWidth;
    canvas.height = sizeHeight;
    canvas.style.width = sizeWidth;
    canvas.style.height = sizeHeight;
}
window.onload = setUpCanvas();

This perfectly sets up your HTML canvas to draw on, and in a responsive manner too :)

mfluehr
  • 2,832
  • 2
  • 23
  • 31
0

The Best Solution is in this awesome video by KIRUPA : https://www.youtube.com/watch?v=UtwyChBp4ig&t=3s

Go and watch it. Canvas resolution is a serious problem. The video will teach you how to fix it.

SomeOne
  • 51
  • 4
  • 2
    While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/late-answers/34051146) –  Mar 22 '23 at 22:00