xinglinkedinenvelopebubblesgoogleplusfacebooktwitterfeedgithub

Panning and scrolling background images using the canvas element

1st March 2014 | by Adam Beres-Deak | javascript, canvas, tutorial

I'm planning to create a simple 2D game in plain JavaScript. As the first step I would like to show, how to animate (pan or scroll) a background image using the canvas element. I am also going to show some basic setup code in order to have a loop where we can draw the frames.

There are two common scenarios for simple 2D games:

Panning the viewport inside the background image

Demo

Please click on the button to start the animation.

How it works

We have a function which is called for every frame our game draws. In this method we calculate the position of the viewport. For this basic example I chose to derive the position from the elapsed time. Therefore the camera takes an elliptical path.

function draw(delta) {
    totalSeconds += delta;
    var x = -1 * (img.width - canvas.width) / 2 * (1 + Math.cos(totalSeconds / Math.PI));
    var y = -1 * (img.height - canvas.height) / 2 * (1 + -Math.sin(totalSeconds / Math.PI));

    context.drawImage(img, x, y);
}

Scrolling the background image infinitely

In the second case the background image is scrolling infinitely as time and the player advances. It's like when playing Mario, but the camera is centered on Mario the whole time.

In the animation above we can see, that for this effect we need at least 2 images (can be the same) or more, depending on the viewport size.

Demo

Please click on the button to start the animation. The vertical black lines mean the edges of the single images. For this example I'm using the same image and we have a constant speed of 100 pixels/sec.

How it works

The background position is also derived from the elapsed time (constant speed).

  1. We calculate how many images are needed to cover the viewport: Math.ceil(canvas.width / img.width) + 1
  2. We calculate the current X-position: totalSeconds * vx % img.width. Please note the modulo operator here.
  3. We store the current context state and translate our canvas to make the drawing easier.
  4. We draw the images - one after the other.
  5. We restore the context's state.

    function draw(delta) {
     totalSeconds += delta;
    
     var vx = 100; // the background scrolls with a speed of 100 pixels/sec
     var numImages = Math.ceil(canvas.width / img.width) + 1;
     var xpos = totalSeconds * vx % img.width;
    
     context.save();
     context.translate(-xpos, 0);
     for (var i = 0; i < numImages; i++) {
         context.drawImage(img, i * img.width, 0);
     }
     context.restore();
    }

All the code which calls our draw() function

In order for this to work, we have some more work to do. This is the setup code I used for these examples.

  1. I used some basic requestAnimationFrame polyfill
  2. The animation gets only started after the image is loaded successfully (onload).
  3. Some start/stop logic and button
  4. And the loop() function which gets called in every frame when our animation is running. Here we need requestAnimationFrame().
(function() {
    window.requestAnimationFrame = window.requestAnimationFrame
            || window.webkitRequestAnimationFrame
            || window.mozRequestAnimationFrame
            || function(callback) { window.setTimeout(callback, 1000 / 60); };

    var canvas = document.getElementById('bg');
    var context = canvas.getContext('2d');
    var looping = false;
    var totalSeconds = 0;

    var img = new Image();
    img.onload = imageLoaded;
    img.src = 'IMG_SOURCE';

    function imageLoaded() {
        draw(0);

        var btn = document.getElementById('btnStart');
        btn.addEventListener('click', function() {
            startStop();
        });
    }

    var lastFrameTime = 0;

    function startStop() {
        looping = !looping;

        if (looping) {
            lastFrameTime = Date.now();
            requestAnimationFrame(loop);
        }
    }

    function loop() {
        if (!looping) {
            return;
        }

        requestAnimationFrame(loop);

        var now = Date.now();
        var deltaSeconds = (now - lastFrameTime) / 1000;
        lastFrameTime = now;
        draw(deltaSeconds);
    }


    function draw(delta) {
        /* Here happens some magic. */
    }
}());

In order to see the full source code, please view these pages

  1. Panning the viewport inside the background image
  2. Scrolling the background image

by Adam Beres-Deak

| Share | Tweet | Share | Share

Latest blog posts

Displaying icons with custom elements 14th October 2015

Plain JavaScript event delegation 26th January 2015

After the first year of blogging - what happened on my blog in 2014? 1st January 2015

Better webfont loading with using localStorage and providing WOFF2 support 18th December 2014

Worth watching: Douglas Crockford speaking about the new good parts of JavaScript in 2014 20th October 2014