How to Fix Blurry Text on HTML Canvases on Mobile Phones
Tags: javascript, gamedev, todayilearned, mobile
Published: 2020-12-07 08:19:31

---
title: How to Fix Blurry Text on HTML Canvases on Mobile Phones
published: true
description: I'm building a social media network and collaboration tool based on mind maps, documenting my work in this series of blog posts
tags: javascript,gamedev,todayilearned,mobile
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/op0hhkycnfs5zoe4umqe.png
series: Nuffshell Coding Diary
---

*I'm building a social media network and collaboration tool based on mind maps, documenting my work in this series of blog posts. Follow me if you're interested in what I've learned along the way about building web apps with React, Tailwind CSS, Firebase, Apollo/GraphQL, three.js and TypeScript.*

Today's episode is about one particular problem I've encountered and how I've fixed it:

# The Problem

In the previous episodes, I've made a mind map using the 3D graphics library three.js ([part I](https://dev.to/pahund/drawing-a-mind-map-with-three-js-and-react-nuffshell-coding-diary-part-i-1g6n), [part II](https://dev.to/pahund/drawing-a-mind-map-with-three-js-and-react-for-real-this-time-nuffshell-coding-diary-part-ii-1n9m), [part III](https://dev.to/pahund/drawing-a-mind-map-with-three-js-and-react-for-real-this-time-nuffshell-coding-diary-part-iii-1cj1), [part IV](https://dev.to/pahund/drawing-a-mind-map-with-three-js-and-react-force-directed-graphs-nuffshell-coding-diary-part-iv-1b74)).

Here's the code of the previous version that has the problem:

{% codesandbox e7l4k %}

Now, if you take a look at this with a regular monitor with, for example, full HD resolution (1920x1080), this will look like this:

![Old version with regular screen](https://dev-to-uploads.s3.amazonaws.com/i/0ldzmm2dwydcty3aoiaa.png)

Looks OK, *however*, when I look at it on my iPhone, it looks like this:

![Old version with Retina screen](https://dev-to-uploads.s3.amazonaws.com/i/t30xn7m4mstvu6xzz2rh.jpeg)

Ugh, you can hardly decipher the label texts!

# The Cause

My phone has more pixels per inch ([PPI](https://www.digitalcitizen.life/simple-questions-what-ppi-and-does-it-matter/)) than my monitor. Apple calls this a “Retina” screen, but Android phones and tablets have this, as well. 

My mind map is rendered on an [HTML canvas element](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API). 

While the high PPI rate on my phone makes sure that the regular text on web pages is rendered more sharply (less pixelated), with HTML canvases, the effect is the opposite – the browser renders the content on the canvas as if the screen were regular, low PPI. Actually, even worse, it seems to additionally blur the content. It does not matter which browser, my mind map looks equally bad on Chrome and Safari.

# The Solution

I've found the solution on this page:

* [Handling High DPI (Retina) displays in WebGL](https://www.khronos.org/webgl/wiki/HandlingHighDPI)

The trick is as follows:

1. Detect if your canvas is being rendered on a device with high PPI
1. If the device has high PPI, increase the canvas size
1. To make sure that the bigger canvas *looks* to be the same size, use CSS styling to make it smaller

# The Code

I've come up with this function that creates a canvas suitable to each device:

```javascript
function createCanvas(width, height, set2dTransform = true) {
  const ratio = Math.ceil(window.devicePixelRatio);
  const canvas = document.createElement('canvas');
  canvas.width = width * ratio;
  canvas.height = height * ratio;
  canvas.style.width = `${width}px`;
  canvas.style.height = `${height}px`;
  if (set2dTransform) {
    canvas.getContext('2d').setTransform(ratio, 0, 0, ratio, 0, 0);
  }
  return canvas;
}
```

My `createCanvas` function accepts three arguments:

* `width`, `height`: the dimensions of the canvas, the way it *looks* on the screen
* `set2dTransform`: this toggles a specific line of code that uses [setTransform](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setTransform) to make sure 2D rendering on the canvas works correctly – `true` by default

Why the `set2DTransform` argument? I've discovered that the 3D graphics library [three.js](https://threejs.org), which I'm using, throws an error if you set 2D transform, that's why I made it optional.

The function calls [window.devicePixelRatio](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio) to determine if we have a regular or high PPI screen. On my monitor, the value is `1`, on my iPhone, it's `2`.

Some devices have fractional device pixel ratios, which can cause rendering issues, so I'm using [Math.ceil](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil) to round up to the next integer.

We then set the width and height of the canvas by multiplying the input width and height with the device pixel ratio. Given width 800 and height 600, on my monitor, the canvas dimensions will be 800x600, on my phone, they will be 1600x1200.

We then set the width and height at which the canvas appears on the screen using `style.width` and `style.height` (in my example, this will be 800x600, regardless of device pixel ratio).

# The result

Here's my Retina-compatible mind map:

{% codesandbox t6hf8 %}

Screenshot from my phone:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/7rlumpx9v0rqf76ip48e.jpeg)

👍🏻 Looks great!

# Acknowledgements

❤️ Thanks to Reddit user [SydBal](https://www.reddit.com/user/SydBal/) for pointing out the problem ([see discussion](https://www.reddit.com/r/javascript/comments/k7cxk2/rendering_react_components_as_a_mind_map_in/geqfoi7?utm_source=share&utm_medium=web2x&context=3))!

❤️ Thanks to StackOverflow user [mynameisko](https://stackoverflow.com/users/1146545/mynameisko) for their [post on SO](https://stackoverflow.com/a/15666143/1253156) that is the base for my solution!

# To Be Continued…

I'm planning to turn my mind map into a social media network and collaboration tool and will continue to blog about my progress in follow-up articles. Stay tuned!