简体   繁体   中英

canvas performance drop after a few seconds

I wrote the following 1000 bounding squares demo as a test of the capabilities of HTML5 canvas. It runs fine at first but then a noticeable drop in fps after a few seconds. I am not sure why. Any pointers would be appreciated.

var c = document.getElementById("canvas");
var context = c.getContext("2d");

var WIDTH = 600;
var HEIGHT = 800;

c.width = WIDTH;
c.height = HEIGHT;

image = loadImage("square.png");

function loadImage(imageName){
    var i = new Image();
    i.src = imageName;
    return i;
}


function clear(){
    context.fillStyle = "#d0e7f9";
    context.rect(0,0,WIDTH,HEIGHT);
    context.fill();
}
clear();

var SpriteList = [];

var Sprite = (function() { //javascript class(?)... shredders
    function Sprite(){  //constructor
        this.x = Math.random()*WIDTH;
        this.y = Math.random()*HEIGHT;
        this.vx = Math.random()*10;
        this.vy = Math.random()*10;
        SpriteList.push(this);
    }

    Sprite.prototype.update = function(){
        this.x += this.vx;
        this.y += this.vy;

        if (this.x<0 || this.x>WIDTH){
            this.vx *= -1;
        }
        if (this.y<0 || this.y>HEIGHT){
            this.vy *= -1;
        }
    };

    return Sprite;
})();

for (var i = 0;i<1000;i++){
    new Sprite();
}

function draw(){
    clear();
    for (i in SpriteList)
    {
        var s = SpriteList[i];
        s.update();
        context.drawImage(image, s.x, s.y);
    }
}

setInterval(draw,1000/60);

There are a few issues with the code but the main reason for this to happen is this code:

This code will require you to use beginPath() :

function clear(){
    context.fillStyle = "#d0e7f9";
    context.beginPath();
    context.rect(0,0,WIDTH,HEIGHT); /// this will require beginPath();
    context.fill();
}

or to avoid it, you can simply modify the code to do this:

function clear(){
    context.fillStyle = "#d0e7f9";
    context.fillRect(0,0,WIDTH,HEIGHT); /// this does not require beginPath();
}

Live fiddle here

/// use a var here    
var image = loadImage("square.png");

/// your image loader is missing - image may not show up
function loadImage(imageName){
    var i = new Image();
    i.onload = nextStep; /// something like this
    i.src = imageName;
    return i;
}

var SpriteList = [];

/// create this as an object
function Sprite(){  //constructor
    this.x = Math.random()*WIDTH;
    this.y = Math.random()*HEIGHT;
    this.vx = Math.random()*10;
    this.vy = Math.random()*10;
    return this;
}

Sprite.prototype.update = function(){
    this.x += this.vx;
    this.y += this.vy;

    if (this.x<0 || this.x>WIDTH){
        this.vx *= -1;
    }
    if (this.y<0 || this.y>HEIGHT){
        this.vy *= -1;
    }
};

/// separate pushing of the instances
for (var i = 0;i<1000;i++){
    SpriteList.push(new Sprite());
}

var oldTime = 0;

function draw(timeElapsed){ /// in milliseconds
    clear();

    var diffTime = timeElapsed - oldTime;

    /// use vars here too
    for (var i = 0, s; s = SpriteList[i]; i++ )
    {
        s.update();
        context.drawImage(image, s.x, s.y);
    }

    oldTime = timeElapsed;

    /// use rAF here
    requestAnimationFrame(draw);
}

draw(0); /// start

The setInterval may cause the whole thing to stack calls if the browser is not fast enough processing the sprites within the time budget you give,.

By using rAF the browser will only request a frame when it can even if that means lower frame rates - you will at least not lock up/slow down the browser.

(as you didn't provide a link to the image you're using I substituted it with a temp canvas - you will still need to consider a onload event handler for the actual image).

A few suggestions:

Use image.onload to be sure your square.png is fully loaded before it's used.

Put the image loading at the bottom of your code after you create your 1000 sprites.

var image=new Image();
image.onload=function(){
    draw();
}
image.src="square.png";

Don't iterate using for(i in SpriteList). Do this instead:

for(var i=0;i<SpriteList.length;i++)

Your draw functions are probably stacking--the current draw() isn't being completed before setInterval is requesting another draw().

Replace setInterval with requestAnimationFrame to stop your stacking problems.

function draw(){

    // request another animation frame
    requestAnimationFrame(draw);

    // draw the current frame
    clear();
    for(var i=0;i<SpriteList.length;i++)
    {
        var s = SpriteList[i];
        s.update();
        context.drawImage(image, s.x, s.y);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM