简体   繁体   中英

HTML5 Canvas flicker in FireFox 4

I'm working on a proof of concept on an HTML5 canvas. I was able to get this working like a charm in Chrome and IE9, but in Firefox 4 I'm getting constant flicker as it redraws the canvas. I've tried a few techniques mentioned on this site like double buffering but I'm still getting a large amount of flicker. Any insight on this would be appreciated!

<!DOCTYPE HTML>
<html>
<head>
<style type="text/css">  
body
{  
    margin:0px;  
    padding:0px;  
    text-align:center;  
} 
canvas
{  
    outline:0;  
    border:1px solid #000;  
    margin-top: 10px;
    margin-left: auto;  
    margin-right: auto;  
}  
</style>
<script type="text/javascript">
var canvasWidth = 640;
var thisXPos = 0;
var canvasHeight = 820;
var thisYPos = 0;
var canvas = null;
var context = null;
var gLoop = null;
var rain = [];
var rainImg = "images/raindrop.gif";
var bgImg = null;
var i;

for (i = 0; i < howManyLetters; i++)
{
    rain.push([Math.floor(Math.random() * canvasWidth), Math.floor(Math.random() * canvasHeight),rainImg]);
}

var DrawRain = function()
{
    for (i = 0; i < 10; i++)
    {
        thisXPos = rain[i][0];
        thisYPos = rain[i][1];
        imgSrc = rain[i][2];
        letterImg = new Image();
        letterImg.setAtX = thisXPos;
        letterImg.setAtY = thisYPos;
        letterImg.onload = function()
        {
            context.drawImage(this, this.setAtX, this.setAtY);
        }
        letterImg.src = imgSrc;
    }
};

var MoveRain = function(e)
{
    for (i = 0; i < 10; i++)
    {
        if ((rain[i][1] - 5) > canvasHeight)
        {
            randomnumber = Math.floor(Math.random()*26);

            rain[i][0] = Math.random() * canvasWidth;
            rain[i][1] = 0;
            rain[i][2] = rainImg;
        }
        else
        {
            rain[i][1] += e;
        }
    }
};

var clear = function()
{
    context.beginPath();
    context.rect(0, 0, canvasWidth, canvasHeight);
    context.closePath();

    bgImg = new Image();
    bgImg.src = "images/bg.png";
    bgImg.onload = function()
    {
        context.drawImage(bgImg,0,0);
    }
} 

var GameLoop = function()
{
    context.save();
    clear();
    MoveRain(1);
    DrawRain();
    context.restore();

    gLoop = setTimeout(GameLoop, 10);
}

function loadGame()
{
    canvas = document.getElementById("gameCanvas");
    context = canvas.getContext("2d");
    canvas.width = canvasWidth;
    canvas.height = canvasHeight;

    GameLoop();
}

</script>
</head>
<body onload="loadGame();">
    <canvas id="gameCanvas"></canvas>
</body>
</html>

I have distilled your example down to this:

http://jsfiddle.net/sPm3b/6/

And it works very fast in Firefox and Chrome.

So we know that the problem lies in the images.

You need to optimize how they are created and loaded. Right now, each clear() creates a new image and waits for it to load! That image should be created only once, in your loadGame() and then reused over and over.

Same exact deal with letterImg in DrawRain() . Move the creation of it to loadGame()

That will probably fix the problem.

EDIT:

like this:

At the top add:

var letterImg = new Image();
var bgImg = new Image();

Then

function loadGame()
{
    canvas = document.getElementById("gameCanvas");
    context = canvas.getContext("2d");
    canvas.width = canvasWidth;
    canvas.height = canvasHeight;

    bgImg.src = "images/bg.png";
    letterImg.src = "images/raindrop.gif";

    // optional: wait for them to load here

    GameLoop();
}

Then drawRain, for example, would look like this:

var DrawRain = function()
{
    for (i = 0; i < 10; i++)
    {
        thisXPos = rain[i][0];
        thisYPos = rain[i][1];
        context.drawImage(letterImg, thisXPosX, thisYPos); // letterImg was already constructed, no need to make it again
    }
};

In complement to Simon Sarris response. I've used a 'double canvas' technique to avoid screen fickering with heavy canvas.

The way it works is always have 2 version of the canvas, one in DOM, one outside, and always draw on the one which is not in DOM . I use it with a redraw queue.

here's a part of a working code

(...)
clear: function() {
    //rotating on 2 canvas, one for draw (outside DOM) one for show
    var self = this;
    if (null == self.canvasbackup) {
        var tmpcanvas = self.canvas.clone(true);
        self.canvasbackup = self.canvas;
        self.canvas=tmpcanvas;
    } else {
        var tmpcanvas = self.canvasbackup;
        self.canvasbackup = self.canvas;
        self.canvas=tmpcanvas;
    }
    self.ctx = self.canvas[0].getContext('2d');
    self.ctx.clearRect( 0, 0, self.options.width, self.options.height );
    jQuery.each(self.elements,function(idx,elt){
        // custom function: my elements need to know which canvas they depends on
        elt.reconnectCanvas(self.canvas,self.ctx);
    });
},
inDOM: function() {
    var self = this;
    if(null==self.canvasbackup) {
        //1st time need to get all things in DOM
        self.canvas.appendTo(self.div);
        self.div.appendTo(self.container);
    } else {
        // remove current shown canvas
        self.connectHuman();
        self.canvasbackup.remove();
        // loosing some events here...
        self.canvas.appendTo(self.div);
        // div is already in DOM, we are in redraw
    }
},
redraw: function() {
    var self = this;
    self.clear();
    jQuery.each(self.elements,function(idx,elt){
        elt.draw();
        elt.enddraw();
    });
    self.inDOM();
}
(...)

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