DEV Community

Jess
Jess

Posted on • Updated on

Resizing HTML5 Canvas and Scaling Sprites

Resizing HTML5 Canvas and Scaling Sprites

I'm working on a game using HTML5/JavaScript and I ran into a problem: My sprites were created as 8x8px and that would be way too small to view on high resolution displays. Not only that, I wanted the size of the canvas to adapt to whatever display it was being viewed on without distorting my sprites, and I didn't want the canvas to take up the entire window.

8x8 sprite
Example of 8x8px sprite. So tiny!

I wrote a sample to show how to go about scaling the canvas to your browser window up to a max height/width, which you can view on codepen or clone from github. I'll explain it below:

First, create a <canvas> inside a container <div>.

 <div id="game">
  <canvas id="canvas"></canvas>
</div>
Enter fullscreen mode Exit fullscreen mode

Next, you'll need to add some styling to get the canvas to stay in the center. Remove margin from <body> and <html> and make it take up 100% of the window. Then add flexbox to your container <div> and give it a height and width of 100% as well.

html,
body {
  margin: 0;
  height: 100%;
  width: 100%;
}

#game {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  height: 100%;
  width: 100%;
}
Enter fullscreen mode Exit fullscreen mode

Now get a reference to your <canvas> and canvas context, and set the initial width and height. SIZE refers to the sprite's size, which is 8 pixels wide by 8 pixels tall. map refers to my 2D array. How to create the map is out of the scope of this post, but you can check out the source code to see how it's done.

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

// size of each 'tile'
const SIZE = 8;

// native width and height (does not change)
const nHeight = SIZE * map.length;
const nWidth = SIZE * map[0].length;

function render() {
  tiles.forEach(tile => {
    ctx.drawImage(tilesheet, tile.srcX, tile.srcY, SIZE, SIZE, tile.x, tile.y, SIZE, SIZE)
  })
}

window.addEventListener('load', () => {
  ctx.canvas.width = nWidth;
  ctx.canvas.height = nHeight;
  buildMap();
  render();
})

Enter fullscreen mode Exit fullscreen mode

At this point your canvas is going to be very tiny since it's using your native game resolution (which, in this case is 56x40). Your native game resolution does not change. It's used for your game's logic, such as character movement, collision, etc.

Example of the game map at native resolution of 56x40
Example of the game map at native resolution of 56x40

In order to scale your canvas you'll want to keep track of a separate width and height for your canvas resolution. If you don't want your canvas to extend to the width/height of the window you can add your own max width/height and/or add a percentage of the window that you want to be taken up. You can play around with this; you might not need or want both or either of these settings.

// I just picked 20 at random here.
// In this instance maxWidth = 56 * 20 = 1,120 and maxHeight = 40 * 20 = 800
const maxMultiplier = 20;
const maxWidth = nWidth * maxMultiplier;
const maxHeight = nHeight * maxMultiplier;

// % of browser window to be taken up by the canvas
// this can just be set to 1 if you want max height or width
const windowPercentage = 0.9;

// the canvas' displayed width/height
// this is what changes when the window is resized 
// initialized to the native resolution
let cHeight = nHeight;
let cWidth = nWidth;
Enter fullscreen mode Exit fullscreen mode

Now when you load, you want to set your canvas dimensions to the variables you're using for your canvas resolution as opposed to your native resolution. You also want to set up an event listener to watch for resize, and in there you want to handle your canvas resizing as well as re-render.

window.addEventListener('load', () => {
  // initialize native height/width
  ctx.canvas.width = cWidth;
  ctx.canvas.height = cHeight;
  buildMap();
  resize();
  render();
})

window.addEventListener('resize', () => {
  resize();
  render();
})

function resize() {
  cWidth = window.innerWidth;
  cHeight = window.innerHeight;

  // ratio of the native game size width to height
  const nativeRatio = nWidth / nHeight;
  const browserWindowRatio = cWidth / cHeight;

  // browser window is too wide
  if (browserWindowRatio > nativeRatio) {

    cHeight = Math.floor(cHeight * windowPercentage); // optional
    if (cHeight > maxWidth) cHeight = maxHeight; // optional

    cWidth = Math.floor(cHeight * nativeRatio);
  } else {
    // browser window is too high

    cWidth = Math.floor(cWidth * windowPercentage); // optional
    if (cWidth > maxWidth) cWidth = maxWidth; // optional

    cHeight = Math.floor(cWidth / nativeRatio);
  }

  // set the canvas style width and height to the new width and height
  ctx.canvas.style.width = `${cWidth}px`;
  ctx.canvas.style.height = `${cHeight}px`;
}

Enter fullscreen mode Exit fullscreen mode

In resize(), you first set cWidth and cHeight (the canvas resolution variables) to the window's innerWidth and innerHeight. Then you need to get the ratio of width to height for both your native resolution and your browser window. If the ratio of the window's innerWidth / innerHeight (cWidth/cHeight) is greater than the native resolution ratio (nWidth/nHeight) then the browser is too wide and you need to recalculate the width so it meets the correct ratio. Otherwise, the browser window is too high and you need to recalculate the height.

If you want to set a max window percentage you can set the new canvas width/height by first multiplying their values by maxPercentage. If you want to use a maxWidth/maxHeight you can check if the cWidth and cHeight values are greater, and if so, set them to their max. After, or instead of, all of that, you can set the final new width or height values (depending on which condition you hit). The new cWidth is calculated by multiplying cHeight by nativeRatio and the new cHeight is calculated by dividing cWidth by nativeRatio.

Next, set the canvas' style property's width and height to the new cWidth and cHeight. "Canvas.width and canvas.height set the size of the canvas. canvas.style.width and canvas.style.height set the resolution."

Finally, add image-rendering: pixelated to your canvas styles so that your pixel art isn't blurry.

canvas {
    image-rendering: pixelated;
}
Enter fullscreen mode Exit fullscreen mode
Example of the game map at native resolution of 56x40
Example of the game map upscaled to approx. 864x617

That should be it. I know I assumed a lot of prior knowledge with canvas and setting up sprites and the game map, so if you're struggling leave a comment and maybe I, or another reader, can help!


Here are some links if you're interested in reading or watching more about this topic.

Top comments (0)