仅在设置新帧时将动画绘制到画布?

时间:2016-05-27 10:00:27

标签: javascript html5 animation canvas requestanimationframe

我有一些JavaScript代码在画布上使用基于精灵的动画,我试图看看我是否可以提高效率。我已经在使用requestAnimationFrame,但由于它是基于交互的,我还试图弄清楚如何在设置新帧时绘制帧。但是,无论我尝试什么,即使动画未运行,它仍然似乎正在绘制新帧。有时甚至改变代码似乎会增加CPU使用率。我真的很困惑这里发生了什么。

这是原始代码:

function CanvasSprite(canvas, width, height, sprite_url, rows, columns, total_frames) {
  	this.canvas = canvas;
    this.width = width;
 	this.height = height;
  	this.rows = rows;
	this.columns = columns;
	this.total_frames = total_frames;
	this.frame = 0;

    var scope = this,
    	func = function(){
    			scope.onSpriteSheet.call(scope);
        		}
    this.load('img', 'spritesheet', sprite_url, func);
};

CanvasSprite.prototype.onSpriteSheet = function() {
  	this.sw = this.spritesheet.width / this.columns;
  	this.sh = this.spritesheet.height / this.rows;
    this.tick(new Date().getTime());
};

CanvasSprite.prototype.load = function(type, prop, url, callback) {
  	this[prop] = document.createElement(type);
  	this[prop].addEventListener('load', callback);
    this[prop].src = url;
};
	
CanvasSprite.prototype.draw = function() {
	var relativeFrame = Math.round(this.frame * (this.total_frames-1));	
	var column_frame = relativeFrame % this.columns;
  			
  	var sx = this.sw * column_frame;
  	var sy = this.sh * Math.floor(relativeFrame / this.columns);

	var context = this.canvas.getContext('2d');
	context.clearRect(0, 0, this.width, this.height);
  	context.drawImage(this.spritesheet, sx, sy, this.sw, this.sh, 0, 0, this.width, this.height);
};
		
CanvasSprite.prototype.tick = function(time) {
      var scope = this,
      func = function(time){
        scope.draw(time || new Date().getTime());
        requestAnimationFrame(func, scope.id);
        //console.log("drawing");
      };
      func();
};

CanvasSprite.prototype.setFrame = function(frame) {
	this.frame = frame;
    //this.tick(new Date().getTime());
    //putting tick() here actually makes it slower :p
};

尝试进一步修改它:

function CanvasSprite(canvas, width, height, sprite_url, rows, columns, total_frames) {
  	this.canvas = canvas;
    this.width = width;
 	this.height = height;
  	this.rows = rows;
	this.columns = columns;
	this.total_frames = total_frames;
    
    this.frameOld = null; //old frame for comparison
	this.frame = 0;

    var scope = this,
    	func = function(){
    			scope.onSpriteSheet.call(scope);
        		}
    this.load('img', 'spritesheet', sprite_url, func);
};

CanvasSprite.prototype.onSpriteSheet = function() {
  	this.sw = this.spritesheet.width / this.columns;
  	this.sh = this.spritesheet.height / this.rows;
    if(this.frame != this.frameOld) {
        this.tick(new Date().getTime()); //only call tick when new frame differs from old
    };
};

CanvasSprite.prototype.load = function(type, prop, url, callback) {
  	this[prop] = document.createElement(type);
  	this[prop].addEventListener('load', callback);
    this[prop].src = url;
};
	
CanvasSprite.prototype.draw = function() {
	var relativeFrame = Math.round(this.frame * (this.total_frames-1));	
	var column_frame = relativeFrame % this.columns;
  			
  	var sx = this.sw * column_frame;
  	var sy = this.sh * Math.floor(relativeFrame / this.columns);

	var context = this.canvas.getContext('2d');
	context.clearRect(0, 0, this.width, this.height);
  	context.drawImage(this.spritesheet, sx, sy, this.sw, this.sh, 0, 0, this.width, this.height);
};
		
CanvasSprite.prototype.tick = function(time) {
      var scope = this,
      func = function(time){
        scope.draw(time || new Date().getTime());
        requestAnimationFrame(func, scope.id);
        console.log("drawing");
      };
      func();
};

CanvasSprite.prototype.setFrame = function(frame) {
    this.frameOld = this.frame; //update frameOld with previous one
	this.frame = frame; //set new frame
};

无论我做什么,控制台都表示即使我没有更新帧,它也会与系统时钟同步。 CPU配置文件也反映了这一点。好像我在这里缺少一些重要的东西。也许canvas已经在幕后进行了优化,所以我的JS没有什么不同,或者只是用不必要的逻辑来减慢它?

1 个答案:

答案 0 :(得分:0)

自己解决了!首先,使用isDirty标志而不是尝试比较帧。然后,由于交互实际上以子帧间隔更新动画,因此将计算要绘制的实际帧的行移动到setFrame函数中。还将同步动画的tick函数移动到setFrame,并在加载时使用对该函数的调用以从零开始。这样就不会经常传递新的日期和时间。最后,测试是否绘制新帧的条件需要在调用draw函数的闭包内。这是因为requestAnimationFrame实际上比任何对画布的调用都要大得多。

结果呢?在没有更新时没有CPU使用,并且在以下情况下大幅减少;)

function CanvasSprite(canvas, width, height, sprite_url, rows, columns, total_frames) {
  	this.canvas = canvas;
    this.width = width;
 	this.height = height;
  	this.rows = rows;
	this.columns = columns;
	this.total_frames = total_frames;
    this.isDirty = true;

    var scope = this,
    	func = function(){
    			scope.onSpriteSheet.call(scope);
        		}
    this.load('img', 'spritesheet', sprite_url, func);
};

CanvasSprite.prototype.onSpriteSheet = function() {
  	this.sw = this.spritesheet.width / this.columns;
  	this.sh = this.spritesheet.height / this.rows;
    this.setFrame(0);
};

CanvasSprite.prototype.load = function(type, prop, url, callback) {
  	this[prop] = document.createElement(type);
  	this[prop].addEventListener('load', callback);
    this[prop].src = url;
};
	
CanvasSprite.prototype.draw = function() {	
	var column_frame = this.frame % this.columns;
  	var sx = this.sw * column_frame;
  	var sy = this.sh * Math.floor(this.frame / this.columns);
    
	var context = this.canvas.getContext('2d');
    context.clearRect(0, 0, this.width, this.height);
    context.drawImage(this.spritesheet, sx, sy, this.sw, this.sh, 0, 0, this.width, this.height);
};
		
CanvasSprite.prototype.tick = function(time) {
    var scope = this,
    func = function(time){
        if (scope.isDirty) {
            scope.draw(time || new Date().getTime());
            requestAnimationFrame(func, scope.id);
            scope.isDirty = false;
            //only draw to canvas when new frame differs from old
        };
    };
    func();
};

CanvasSprite.prototype.setFrame = function(frame) {
    var tempFrame = Math.round(frame * (this.total_frames-1));;
    if(tempFrame != this.frame) {
        this.frame = tempFrame;
        this.isDirty = true;
        this.tick(new Date().getTime());
    }
};