核心提示:尽量不要使用setInterval和setTimeout来实现动画,而应该使用requestAnimationFrame()方法来让浏览器自行决定帧速率。由于各个浏览器对requestAnimatio...
尽量不要使用setInterval和setTimeout来实现动画,而应该使用requestAnimationFrame()方法来让浏览器自行决定帧速率。
由于各个浏览器对requestAnimationFrame()的实现有所区别,首先封装一个兼容函数requestNextAnimationFrame :
window.requestNextAnimationFrame = (function () { var originalWebkitRequestAnimationFrame = undefined, wrapper = undefined, callback = undefined, geckoVersion = 0, userAgent = navigator.userAgent, index = 0, self = this; // Workaround for Chrome 10 bug where Chrome // does not pass the time to the animation function if (window.webkitRequestAnimationFrame) { // Define the wrapper wrapper = function (time) { if (time === undefined) { time = +new Date(); } self.callback(time); }; // Make the switch originalWebkitRequestAnimationFrame = window.webkitRequestAnimationFrame; window.webkitRequestAnimationFrame = function (callback, element) { self.callback = callback; // Browser calls the wrapper and wrapper calls the callback originalWebkitRequestAnimationFrame(wrapper, element); } } // Workaround for Gecko 2.0, which has a bug in // mozRequestAnimationFrame() that restricts animations // to 30-40 fps. if (window.mozRequestAnimationFrame) { // Check the Gecko version. Gecko is used by browsers // other than Firefox. Gecko 2.0 corresponds to // Firefox 4.0. index = userAgent.indexOf('rv:'); if (userAgent.indexOf('Gecko') != -1) { geckoVersion = userAgent.substr(index + 3, 3); if (geckoVersion === '2.0') { // Forces the return statement to fall through // to the setTimeout() function. window.mozRequestAnimationFrame = undefined; } } } return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback, element) { var start, finish; window.setTimeout( function () { start = +new Date(); callback(start); finish = +new Date(); self.timeout = 1000 / 60 - (finish - start); }, self.timeout); }; } ) ();
示例: 使用requestAnimationFrame()方法来绘制动画
html
<head> <title>Using requestAnimationFrame()</title> <style> body { background: #dddddd; } #canvas { background: #ffffff; cursor: pointer; margin-left: 10px; margin-top: 10px; -webkit-box-shadow: 3px 3px 6px rgba(0,0,0,0.5); -moz-box-shadow: 3px 3px 6px rgba(0,0,0,0.5); box-shadow: 3px 3px 6px rgba(0,0,0,0.5); } #controls { margin-top: 10px; margin-left: 15px; } </style> </head> <body> <p id='controls'> <input id='animateButton' type='button' value='Animate'/> </p> <canvas id='canvas' width='750' height='500'> Canvas not supported </canvas> <script src='../../shared/js/requestNextAnimationFrame.js'></script> <script src='example.js'></script> </body>
js
var canvas = document.getElementById('canvas'), context = canvas.getContext('2d'), paused = true, //定义三个球 discs = [ { x: 150, y: 250, lastX: 150, lastY: 250, velocityX: -3.2, velocityY: 3.5, radius: 25, innerColor: 'rgba(255,255,0,1)', middleColor: 'rgba(255,255,0,0.7)', outerColor: 'rgba(255,255,0,0.5)', strokeStyle: 'gray', }, { x: 50, y: 150, lastX: 50, lastY: 150, velocityX: 2.2, velocityY: 2.5, radius: 25, innerColor: 'rgba(100,145,230,1.0)', middleColor: 'rgba(100,145,230,0.7)', outerColor: 'rgba(100,145,230,0.5)', strokeStyle: 'blue' }, { x: 150, y: 75, lastX: 150, lastY: 75, velocityX: 1.2, velocityY: 1.5, radius: 25, innerColor: 'rgba(255,0,0,1.0)', middleColor: 'rgba(255,0,0,0.7)', outerColor: 'rgba(255,0,0,0.5)', strokeStyle: 'orange' }, ], numDiscs = discs.length, animateButton = document.getElementById('animateButton'); // Functions..................................................... function drawBackground() { var STEP_Y = 12, i = context.canvas.height; context.strokeStyle = 'lightgray'; context.lineWidth = 0.5; while(i > STEP_Y*4) { context.beginPath(); context.moveTo(0, i); context.lineTo(context.canvas.width, i); context.stroke(); i -= STEP_Y; } context.save(); context.strokeStyle = 'rgba(100,0,0,0.3)'; context.lineWidth = 1; context.beginPath(); context.moveTo(35,0); context.lineTo(35,context.canvas.height); context.stroke(); context.restore(); } function update() { var disc = null; for(var i=0; i < numDiscs; ++i) { disc = discs[i]; //边缘检测 if (disc.x + disc.velocityX + disc.radius > context.canvas.width || disc.x + disc.velocityX - disc.radius < 0) disc.velocityX = -disc.velocityX; if (disc.y + disc.velocityY + disc.radius > context.canvas.height || disc.y + disc.velocityY - disc.radius < 0) disc.velocityY= -disc.velocityY; disc.x += disc.velocityX; disc.y += disc.velocityY; } } function draw() { var disc = discs[i]; for(var i=0; i < numDiscs; ++i) { disc = discs[i]; gradient = context.createRadialGradient(disc.x, disc.y, 0, disc.x, disc.y, disc.radius); gradient.addColorStop(0.3, disc.innerColor); gradient.addColorStop(0.5, disc.middleColor); gradient.addColorStop(1.0, disc.outerColor); context.save(); context.beginPath(); context.arc(disc.x, disc.y, disc.radius, 0, Math.PI*2, false); context.fillStyle = gradient; context.strokeStyle = disc.strokeStyle; context.fill(); context.stroke(); context.restore(); } } // 动画..................................................... function animate(time) { if (!paused) { context.clearRect(0,0,canvas.width,canvas.height); drawBackground(); update(); draw(); window.requestNextAnimationFrame(animate); } } // 初始化................................................ context.font = '48px Helvetica'; animateButton.onclick = function (e) { paused = paused ? false : true; if (paused) { animateButton.value = 'Animate'; } else { window.requestNextAnimationFrame(animate); animateButton.value = 'Pause'; } };
帧速率计算
动画图形的显示频率叫做“帧速率”frame rate。
通常来说,有必要计算一下帧速率。计算方式:
var lastTime = 0; function calculateFps(){ var now=(+new Date),fps = 1000/(now-lastTime); lastTime = now; return fps; } function animate(time){ eraseBackground(); drawBackground(); update(); draw(); context.fillStyle='cornflowerblue'; context.fillText(calculateFps().toFixed()+' fps',20,60); window.requestNextAnimationFrame(animate); } window.requestNextAnimationFrame(animate);
以不同的帧速率来执行各种任务
一些不重要的任务,可以以低速率来执行。下面示例中循环根据上一次更新fps数值的时间来判断当前是否已经过了一秒钟,如果是的话,就再次更新fps数值:
var lastFpsUpdateTime =0, lastFpsUpdate =0; function animate(time){ var fps =0; if(time==undefined){ time = +new Date; } if(!paused){ eraseBackground(); drawBackground(); update(time); draw(); fps=calculateFps(); // 每秒更新frame rate值 if(now - lastFpsUpdateTime>1000){ lastFpsUpdateTime = now; lastFpsUpdate = fps; } context.fillStyle = 'cornflowerblue'; context.fillText(lastFpsUpdate.toFixed() + ' fps',50,48); } }