Resizing HTML5 Canvas and Scaling Sprites
Tags: javascript, gamedev, html, canvas
Published: 2020-09-07 02:35:18

---
title: Resizing HTML5 Canvas and Scaling Sprites
published: true
description: Resizing HTML5 Canvas and Scaling Sprites
tags: javascript, gamedev, html5, canvas
//cover_image: https://direct_url_to_image.jpg
---
# Resizing HTML5 Canvas and Scaling Sprites

I'm working on a game using HTML5/JavaScript and I ran into a problem: My [sprites](https://en.wikipedia.org/wiki/Sprite_(computer_graphics)) 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. 

<center>
	<figure>
		<img src="https://github.com/robotspacefish/resize/blob/master/8x8.png?raw=true" alt="8x8 sprite">
		<figcaption>Example of 8x8px sprite. So tiny!</figcaption>
	</figure>
</center>

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](https://codepen.io/robotspacefish/full/BaKJzVG) or clone from [github](https://github.com/robotspacefish/resize). I'll explain it below:


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

```html
 <div id="game">
  <canvas id="canvas"></canvas>
</div>
```

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.

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

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

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.

```js
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();
})

```

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. 

<center>
	<figure>
		<img src="https://github.com/robotspacefish/resize/blob/master/native_example.png?raw=true" alt="Example of the game map at native resolution of 56x40">
		<figcaption>Example of the game map at native resolution of 56x40</figcaption>
	</figure>
</center>

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.

```js
// 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;
```

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.


```js
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`;
}

```

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.](https://stackoverflow.com/questions/33515707/scaling-a-javascript-canvas-game-properly)"

Finally, add [`image-rendering: pixelated`](https://developer.mozilla.org/en-US/docs/Web/CSS/image-rendering) to your `canvas` styles so that your pixel art isn't blurry.

```css
canvas {
	image-rendering: pixelated;
}
```

<center>
	<figure>
		<img src="https://github.com/robotspacefish/resize/blob/master/upscaled_example.png?raw=true" alt="Example of the game map at native resolution of 56x40">
		<figcaption>Example of the game map upscaled to approx. 864x617</figcaption>
	</figure>
</center>

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!

<hr>

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

* [28- Making HTML5 Game: Resizing Game Part 2. Javascript Tutorial Guide](https://www.youtube.com/watch?v=1y57COMRSdA&t=304s)
* Advanced Game Design with HTML5 and JavaScript by Rex van der Spuy
* Building JavaScript Games for Phones, Tablets, and Desktop by Arjan Egges
* [Scaling a Javascript Canvas Game Properly](https://stackoverflow.com/questions/33515707/scaling-a-javascript-canvas-game-properly)