转载

使用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);
正文到此结束
Loading...