使用requestAnimationFrame()优化JavaScript动画性能
在JavaScript里曾经只有一种方法来设定定时循环任务:setInterval()
。如果你想快速的重复有些动作(但又不是像直接调用for
循环那样立即执行),你就需要用到定时调度。最常见的就是动画绘制过程,当动画的绘制速度达到每秒钟60帧时,动画会显得非常的流畅,于是,你需要允许像下面这样一个定时循环任务:
setInterval(function() { // 做某些动画任务 }, 1000/60);
但现在有了一个新的、性能更好的方法可以实现这个任务。之前我们曾讲过requestAnimationFrame() 这个方法。那篇文章里是一个系统的介绍,今天将在这里举2个实际例子和用法。
为什么requestAnimationFrame()更好?
- 浏览器可以优化并行的动画动作,更合理的重新排列动作序列,并把能够合并的动作放在一个渲染周期内完成,从而呈现出更流畅的动画效果。
- 在一个浏览器标签页里运行一个动画,当这个标签页不可见时,浏览器会暂停它,这会减少CPU,内存的压力,节省电池电量。
最简单的一个使用requestAnimationFrame()的例子
function repeatOften() { // 做某些事情 requestAnimationFrame(repeatOften); } requestAnimationFrame(repeatOften);
这个方法一旦启动,它就会递归的调用自己。
启动和停止
requestAnimationFrame
函数能返回一个ID,根据这个ID,你可以停止它的允许,这就像 setTimeout
和 setInterval
的用法一样。下面是一个实际可运行的例子:
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);
阅读余下内容
标题党。。。
感谢 博主的分享~
。。。。。。。。。。。。。。。。