iygcpa
Last Updated: February 23, 2018
·
19.76K
· bahlor

Gameloop - the correct way

On many pages on the web, people still tend to use setInterval() as main function of a gameloop. This is not the best way to do this. Modern browsers are capable of telling you, as soon as they're ready to render new output using requestAnimationFrame.

Basic example using setInterval()

var canvas = document.getElementById('canvas'),
    cw = canvas.width,
    ch = canvas.height,
    cx = null,
    fps = 30,
    bX = 30,
    bY = 30,
    mX = 10,
    mY = 20;

function gameLoop() {
    cx.clearRect(0,0,cw,cw);

    cx.beginPath();
    cx.fillStyle = 'red';
    cx.arc(bX, bY, 20, 0, Math.PI * 360);
    cx.fill();
    if(bX >= cw || bX <= 0) { mX*=-1; }
    if(bY >= ch || bY <= 0) { mY*=-1; }

    bX+=mX;
    bY+=mY;
}

if (typeof (canvas.getContext) !== undefined) {
    cx = canvas.getContext('2d');

    setInterval(gameLoop, 1000 / fps);
}

jsFiddle Demo

The same using requestAnimationFrame

var vendors = ['webkit', 'moz'];
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
    window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
    window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];
}

var canvas = document.getElementById('canvas'),
    cw = canvas.width,
    ch = canvas.height,
    cx = null,
    fps = 30,
    bX = 30,
    bY = 30,
    mX = 150,
    mY = 300,
    lastTime = (new Date()).getTime(),
    currentTime = 0,
    delta = 0;

function gameLoop() {
    window.requestAnimationFrame(gameLoop);

    currentTime = (new Date()).getTime();
    delta = (currentTime - lastTime) / 1000;
    cx.clearRect(0, 0, cw, cw);

    cx.beginPath();
    cx.fillStyle = 'red';
    cx.arc(bX, bY, 20, 0, Math.PI * 360);
    cx.fill();
    if (bX >= cw || bX <= 0) {
        mX *= -1;
    }
    if (bY >= ch || bY <= 0) {
        mY *= -1;
    }

    bX += (mX * delta);
    bY += (mY * delta);

    lastTime = currentTime;
}

if (typeof (canvas.getContext) !== undefined) {
    cx = canvas.getContext('2d');

    gameLoop();
}

jsFiddle Demo

Another method limiting the rendering to a maximum fps

var vendors = ['webkit', 'moz'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
    window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
    window.cancelAnimationFrame =
      window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}

var canvas = document.getElementById('canvas'),
    cw = canvas.width,
    ch = canvas.height,
    cx = null,
    fps = 30,
    bX = 30,
    bY = 30,
    mX = 10,
    mY = 20,
    interval     =    1000/fps,
    lastTime     =    (new Date()).getTime(),
    currentTime  =    0,
    delta = 0;

function gameLoop() {
    window.requestAnimationFrame(gameLoop);

    currentTime = (new Date()).getTime();
    delta = (currentTime-lastTime);

    if(delta > interval) {

        cx.clearRect(0,0,cw,cw);

        cx.beginPath();
        cx.fillStyle = 'red';
        cx.arc(bX, bY, 20, 0, Math.PI * 360);
        cx.fill();
        if(bX >= cw || bX <= 0) { mX*=-1; }
        if(bY >= ch || bY <= 0) { mY*=-1; }

        bX+=mX;
        bY+=mY;

        lastTime = currentTime - (delta % interval);
    }
}

if (typeof (canvas.getContext) !== undefined) {
    cx = canvas.getContext('2d');

    gameLoop();
}

jsFiddle Demo

4 Responses
Add your response

7774

Very nice! Didn't know this. To bad the cross browser compatibility is not 100% yet =(

over 1 year ago ·
7775

@alebles

Thanks! But with the polyfill by Erik Moeller you're quite ready for older browsers without requestAnimationFrame and up to date browsers, as this includes an alternative setInterval fallback. :)

// requestAnimationFrame polyfill by Erik Moeller
var lastTime = 0;
var vendors = ['webkit', 'moz'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
    window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
    window.cancelAnimationFrame =
      window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}

if (!window.requestAnimationFrame)
    window.requestAnimationFrame = function(callback, element) {
        var currTime = new Date().getTime();
        var timeToCall = Math.max(0, 16 - (currTime - lastTime));
        var id = window.setTimeout(function() { callback(currTime + timeToCall); },
          timeToCall);
        lastTime = currTime + timeToCall;
        return id;
    };

if (!window.cancelAnimationFrame)
    window.cancelAnimationFrame = function(id) {
        clearTimeout(id);
    };
over 1 year ago ·
28831

When I copied your code, the JS++ compiler gave me this error:

"if (typeof (canvas.getContext) !== undefined) {"

"[ ERROR ] JSPPE5000: Cannot convert void' tostring' at line 44 char 35 at main.jspp"

I think you meant to use the string "undefined" instead of the undefined value. After I fixed this, it worked and I get a red bouncing ball. You should really consider using JS++ to catch errors like this.

over 1 year ago ·
29694

i think your clearRect syntax should be like this cx.clearRect(0,0,cw,ch);

over 1 year ago ·