使用requestAnimationFrame()优化JavaScript动画性能

在JavaScript里曾经只有一种方法来设定定时循环任务:setInterval()。如果你想快速的重复有些动作(但又不是像直接调用for循环那样立即执行),你就需要用到定时调度。最常见的就是动画绘制过程,当动画的绘制速度达到每秒钟60帧时,动画会显得非常的流畅,于是,你需要允许像下面这样一个定时循环任务:

setInterval(function() {
// 做某些动画任务
}, 1000/60);

但现在有了一个新的、性能更好的方法可以实现这个任务。之前我们曾讲过requestAnimationFrame() 这个方法。那篇文章里是一个系统的介绍,今天将在这里举2个实际例子和用法。

为什么requestAnimationFrame()更好?

  • 浏览器可以优化并行的动画动作,更合理的重新排列动作序列,并把能够合并的动作放在一个渲染周期内完成,从而呈现出更流畅的动画效果。
  • 在一个浏览器标签页里运行一个动画,当这个标签页不可见时,浏览器会暂停它,这会减少CPU,内存的压力,节省电池电量。

最简单的一个使用requestAnimationFrame()的例子

function repeatOften() {
// 做某些事情
requestAnimationFrame(repeatOften);
}
requestAnimationFrame(repeatOften);

这个方法一旦启动,它就会递归的调用自己。

启动和停止

requestAnimationFrame 函数能返回一个ID,根据这个ID,你可以停止它的允许,这就像 setTimeoutsetInterval 的用法一样。下面是一个实际可运行的例子:

var globalID;
function repeatOften() {
document.getElementsByTagName("body").appendChild('#');;
globalID = requestAnimationFrame(repeatOften);
}
$("#start").on("click", function() {
globalID = requestAnimationFrame(repeatOften);
});
$("#stop").on("click", function() {
cancelAnimationFrame(globalID);
});

运行效果是这样的:

不要用低于IE9的浏览器观看此演示,你懂得!

一个稍微复杂的使用requestAnimationFrame()的例子

事实上,在使用 canvas 绘画时这样这个函数会更加适合,能获得更好的结果:

这个演示的复杂在于好几种动画的同时发生。

JavaScript代码

var snake = {
canvas: document.getElementById("canvas"),
ctx: document.getElementById("canvas").getContext('2d'),
// how big the "squares" will be
xDis: 0,
yDis: 0,
// where the square will be drawn
posX: 0,
posY: 0,
repeater: 0, // ID of requestAnimationFrame
divisions: 30, // breaks frame into X × X squares
init: function() {
// Set up "Two Dimensional" Array to remember what is on and off
this.memory = new Array(this.divisions-1);
for (var i = 0; i < (this.divisions+1); i++) {
this.memory[i] = new Array(this.divisions-1);
}
// Size the canvas appropriately
var width = window.innerWidth;
var height = window.innerHeight;
snake.canvas.width = width;
snake.canvas.height = height;
// Size of squares is canvas width broken into equal chunks
snake.xDis = width/snake.divisions;
snake.yDis = height/snake.divisions;
// All pink, baby
this.ctx.fillStyle = "#EA80B0";
// Random starting position
this.posX = Math.floor(Math.random() * this.divisions);
this.posY = Math.floor(Math.random() * this.divisions);
// global
drawLoop = function() {
snake.repeater = requestAnimationFrame(drawLoop);
snake.oneMovement();
}
drawLoop();
},
drawSquare: function(x, y) {
// Actually draw it
snake.ctx.fillRect(x*this.xDis, y*this.yDis, this.xDis, this.yDis);
// Record it in memory
snake.memory[x][y] = true;
},
checkPossiblePositions: function() {
var posToReturn = [];
if (this.posX == 0) {
// can't go left
} else if (this.memory[this.posX-1][this.posY] == true) {
// left occupied
} else {
posToReturn.push("left");
}
if (this.posX == this.divisions) {
// can't go right
} else if (this.memory[this.posX+1][this.posY] == true) {
// right occupied
} else {
posToReturn.push("right");
}
if (this.posY == 0) {
// can't go up
} else if (this.memory[this.posX][this.posY-1] == true) {
// top occupied
} else {
posToReturn.push("up");
}
if (this.posY == this.divisions) {
// can't go down
} else if (this.memory[this.posX][this.posY+1] == true) {
// bottom occupied
} else {
posToReturn.push("down");
}
return posToReturn;
},
startNewRound: function() {
// Stop!
cancelAnimationFrame(this.repeater);
// Find new spot
var newSpot = this.findEmpty();
if (newSpot == "nope") {
// Absolutely done, start over.
// clear canvas
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// start over
this.init();
} else {
// Start over from new position
this.posX = newSpot[0];
this.posY = newSpot[1];
// Actually restart
drawLoop();
}
},
oneMovement: function() {
this.drawSquare(this.posX, this.posY);
var possiblePos = this.checkPossiblePositions();
var numPossible = possiblePos.length;
if (numPossible == 0) {
this.startNewRound();
} else {
var randomDir = Math.floor(Math.random() * numPossible);
if (possiblePos[randomDir] == "left") {
this.posX--;
}
if (possiblePos[randomDir] == "right") {
this.posX++;
}
if (possiblePos[randomDir] == "up") {
this.posY--;
}
if (possiblePos[randomDir] == "down") {
this.posY++;
}
}
},
findEmpty: function() {
for (var x = 0; x < (this.divisions+1); x++) {
for (var y = 0; y < (this.divisions+1); y++) {
if (!this.memory[x][y]) {
return [x, y];
}
}
}
return "nope";
}
}
// need this loop to make sure canvas sizes right on CodePen
setTimeout(function() {
snake.init();
}, 10);
以上是使用requestAnimationFrame()优化JavaScript动画性能的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>