How to Draw Sharp Lines on a HTML5 Canvas for Retina Displays

 
12 Kudos
Don't
move!

Two ways to get 1px wide sharp and crisp lines for retina displays:

  • Scale the canvas to twice the size (context.scale(2,2);) and then draw a 1px wide line like so: context.lineWidth=0.5
  • Use all device pixels and draw the image twice the size. This is the approach I focus on in this blog post.

Here is how to draw super sharp lines on an HTML5 canvas for retina displays:

Step 1: Work with all available device pixels

The canvas width and height attributes must match the actual available device pixels (window.devicePixelRatio). If you want to draw a full width line on an iPhone, it has to be 750 pixels long and not only 375.

But the CSS style of the canvas is only half of that. Check the JsFiddle below to see how I set up the canvas, its style attributes and its container element.

Step 2: Offset path coordinates by 0.5 pixels

Do this:

context.beginPath();
context.moveTo(x1 + 0.5, y1 + 0.5);
context.lineTo(x2 + 0.5, y2 + 0.5);
context.closePath();

It makes sense to wrap your drawing tasks in a small function like drawLine which automatically converts coordinates to half-pixels.

Here’s a JsFiddle to try it out.

Full code:

<div id="container" style="border: 1px dotted red;">
</div>
<script>
let container = document.getElementById("container"),
    canvas = document.createElement("canvas"),
    context = canvas.getContext("2d");

// round pixelRatio, because older devices have a pixelRatio of 1.5. Treat them as @2x devices
let pixelRatio = Math.round(window.devicePixelRatio) || 1;

container.appendChild(canvas);

// we're working with all available pixels, i. e. 750x1334 on an iPhone 7
let width = 375 * pixelRatio;
let height = 667 * pixelRatio;

// here's the magic: the actual canvas has the same pixels as the device (iPhone 7 in this case)
canvas.width = width;
canvas.height = height;
// however, the canvas dimensions are set to the pixels we're working with in the browser.
canvas.style.width = Math.round(width / pixelRatio) + "px";
canvas.style.height = Math.round(height / pixelRatio) + "px";
// also, the container should have the same size as the canvas
container.style.width = canvas.style.width;
container.style.height = canvas.style.height;

// now let's draw stuff
context.clearRect(0, 0, width, height);
context.save();

let x1 = 10,
    y1 = 10,
    x2 = width - 10,
    y2 = 10;

// an actual 1px hairline
context.lineWidth = 1;
context.strokeStyle = "#444";

// place the path
// Important: always offset by half a pixel to make lines super crisp
context.beginPath();
context.moveTo(x1 + 0.5, y1 + 0.5);
context.lineTo(x2 + 0.5, y2 + 0.5);
context.closePath();

context.stroke();

context.restore();
</script>

 

Leave a Reply

Your email address will not be published. Required fields are marked *