I am using the HTML 5 canvas to make a simple platformer game. I am currently drawing the tiles using a for loop that runs through a list of tiles and checks if they will be drawn to the screen.

for (var i = 0; i < tiles.length; i++) {
    if (tiles[i].x + scale > pos.x - (canvas.width / 2) 
            && tiles[i].x < (pos.x + scale) + (canvas.width)) {
        canvas.drawImage(tiles[i].x - pos.x, tiles[i].y - pos.y, scale, scale, 
                "img/Tiles/" + tiles[i].Tile + ".png")

I am also using this custom library to draw the image to the screen:

this.drawImage = function(x, y, width, height, src, alpha) {
    if (alpha) {
        ctx.globalAlpha = alpha

    if (document.getElementById(src) == undefined) {
                += '<img width="16px" height="16px" src=' + src + ' id=' + src + '></img>'

    var img = document.getElementById(src)
    ctx.drawImage(img, x, y, width, height);
    ctx.globalAlpha = 1.0
    document.getElementById(src).outerHTML = ""

When I run the for loop, the frame rate of the game seems to drop. Is there an alternate option to going through every element of the array that I'm not aware of?

  • \$\begingroup\$ Hello, I see you are drawing each image to the screen each time in your loop. This is causing you to call the draw function many times even though you only have one frame. Try drawing all the images onto a kind of back buffer before sending everything to the screen to be drawn in the same Draw() call. The Draw() function is expensive and should only be called once per frame. \$\endgroup\$ – G. Meadows Dec 13 '16 at 18:18

Determine the frustrum (range) of your viewport, then use that to only iterate over visible tiles.

For instance, if your viewport can only see rows 14 - 27 and columns 33 - 89, then your rendering loop should only even consider drawing those tiles. Assuming you have calculated this visible range:

for (let y = bottom; y <= top; ++y)
  for (let x = left; x <= right; ++x)
    draw_tile(x, y);

Calculating that range depends on how you've setup the rest of your drawing code. It might in your case be as easy as taking the eye/camera position, adding/subtracting half the canvas dimensions, and then converting into tile positions (remember to round up/down for tiles partially visible):

const left = floor(camera.x - canvas.width*0.5)
const right = ceil(camera.x + canvas.width*0.5)
const top = ceil(camera.y + canvas.height*0.5)
const bottom = floor(camera.y - canvas.height*0.5)

Tweak as necessary for your vector library and whether your coordinates are +Y up or +Y down (I'm not immediately familiar with what canvas uses).


Fixed by using a seperate loop to evaluate which blocks are on screen before drawing them. This loop runs at 1/4th the speed of the main loop to help reduce lag. Here is the code:

//Variable to hold which tiles are on scree
var currTiles = []

//Calculate which tiles to draw to screen. Runs at 15 fps
function draw2() {
  currTiles = []
  for (var i = 0; i < tiles.length; i++) {
    if (tiles[i].x + scale > pos.x - (canvas.width / 2) && tiles[i].x < (pos.x + scale) + (canvas.width)) {

//Loop to draw tiles in the "currTiles" array. Running at 60 fps
function draw() {
  for (var i = 0; i < currTiles.length; i++) {
    canvas.drawImage(currTiles[i].x - pos.x, currTiles[i].y - pos.y, scale, scale, "img/Tiles/" + currTiles[i].Tile + ".png")

  • 1
    \$\begingroup\$ Your first loop is culling non-visible tiles which is key to good graphics perf in general (don't try to draw things that you don't have to). Your approach to culling here is not efficient, though; testing each individual tile or object will become a bottleneck as your maps increase in size. You want to ignore large chunks of the level at a time using some kind of spatial analysis against the viewport - see my answer for a fast way to do this for the tilemap. For objects, you'll want to look up "spatial partitioning." \$\endgroup\$ – Sean Middleditch Dec 13 '16 at 19:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.