首页 > 代码库 > 秀才提笔忘了字:javascript使用requestAnimationFrame实现动画
秀才提笔忘了字:javascript使用requestAnimationFrame实现动画
requestAnimationFrame优于setTimeout/setInterval的地方在于它是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销,这篇文章给大家详细介绍使用requestAnimationFrame实现js动画:仪表盘效果。
参考链接:http://www.cnblogs.com/libin-1/p/6068340.html
废话不多说,先看看一个效果:
直接上代码:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>canvas仪表盘动画效果</title> <style type="text/css"> html, body { width: 100%; height: 100%; margin: 0; } canvas { display: none; border: 1px solid red; display: block; margin: 0 auto; background: -webkit-linear-gradient(top, #0e83f5 0%, #21bdf6 100%); } </style> <script type="text/javascript"> window.onload = function() { window.requestAnimFrame = (function() { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60); }; })(); var canvas = document.getElementById(‘canvas‘), ctx = canvas.getContext(‘2d‘), cWidth = canvas.width, cHeight = canvas.height, score = canvas.attributes[‘data-score‘].value, radius = 100, //圆的半径 deg0 = Math.PI / 9, //每一格20度 mum = 100, //数字步长 /* * 要求:圆弧走完,数字得自加完,就得确定圆弧走的次数和数字走的次数相等! 数字最大10000,对应的度数是11*PI/9,那每个步长mum对应的度数如下: */ deg1 = mum * Math.PI * 11 / 9 / 10000; // 每mum对应的度数 var angle = 0, //初始角度 credit = 0; //数字默认值开始数 var drawFrame = function() { if(score < 0 || score > 10000) { alert(‘额度只能是0--10000‘) score = 10000; } ctx.save(); ctx.clearRect(0, 0, cWidth, cHeight); ctx.translate(cWidth / 2, cHeight / 2); ctx.rotate(8 * deg0); //160度 var aim = score * deg1 / mum; //数字对应的弧度数,先确定要走几次,除以mum,然后计算对应的弧度数 if(angle < aim) { angle += deg1; } if(credit < score) { credit += mum; //默认数字间隔是mum } else if(credit >= 10000) { credit = 10000; } //信用额度 ctx.save(); ctx.rotate(10 * deg0); ctx.fillStyle = ‘white‘; ctx.font = ‘28px Microsoft yahei‘; ctx.textAlign = ‘center‘; ctx.fillText(‘信用额度‘, 0, 50); ctx.restore(); // text(credit); ctx.save(); ctx.beginPath(); ctx.lineWidth = 5; ctx.strokeStyle = ‘rgba(255, 255, 255, 1)‘; ctx.arc(0, 0, radius, 0, angle, false); //动画圆环 ctx.stroke(); ctx.restore(); ctx.save(); ctx.rotate(10 * deg0); //200度 ctx.restore(); ctx.beginPath(); ctx.strokeStyle = ‘rgba(255, 0, 0, .1)‘; ctx.lineWidth = 5; ctx.arc(0, 0, radius, 0, 11 * deg0, false); //设置外圆环220度 ctx.stroke(); ctx.restore(); window.requestAnimFrame(drawFrame); } function text(process) { ctx.save(); ctx.rotate(10 * deg0); //200度 ctx.fillStyle = ‘red‘; ctx.font = ‘40px Microsoft yahei‘; ctx.textAlign = ‘center‘; ctx.textBaseLine = ‘top‘; ctx.fillText("¥:" + process, 0, 10); ctx.restore(); } setTimeout(function() { document.getElementById("canvas").style.display = "block"; drawFrame(); }, 10) } </script> </head> <body> <canvas id="canvas" width="300" height="300" data-score=‘8100‘></canvas> </body> </html>
使用requestAnimationFrame实现js动画性能好。先给大家简单介绍下requestAnimationFrame比起setTimeout、setInterval有哪些优势?
requestAnimationFrame 比起 setTimeout、setInterval的优势主要有:
1、requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧。
2、在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的的cpu,gpu和内存使用量。
3.浏览器可以优化并行的动画动作,更合理的重新排列动作序列,并把能够合并的动作放在一个渲染周期内完成,从而呈现出更流畅的动画效果。比如,通过requestAnimationFrame()
,JS动画能够和CSS动画/变换或SVG SMIL动画同步发生。另外,如果在一个浏览器标签页里运行一个动画,当这个标签页不可见时,浏览器会暂停它,这会减少CPU,内存的压力,节省电池电量。
基本用法与区别:
setTimeout(code, millseconds)
用于延时执行参数指定的代码,如果在指定的延迟时间之前,你想取消这个执行,那么直接用clearTimeout(timeoutId)
来清除任务,timeoutID
是setTimeout
时返回的;setInterval(code, millseconds)
用于每隔一段时间执行指定的代码,永无停歇,除非你反悔了,想清除它,可以使用clearInterval(intervalId)
,这样从调用 clearInterval 开始,就不会在有重复执行的任务,intervalId
是setInterval
时返回的;requestAnimationFrame(code)
,一般用于动画,与setTimeout
方法类似,区别是setTimeout
是用户指定的,而requestAnimationFrame
是浏览器刷新频率决定的,一般遵循 W3C 标准,它在浏览器每次刷新页面之前执行。
先看实现思路:
最简单:
var FPS = 60; setInterval(draw, 1000/FPS);
这个简单做法,如果draw带有大量逻辑计算,导致计算时间超过帧等待时间时,将会出现丢帧。除外,如果FPS太高,超过了当时浏览器的重绘频率,将会造成计算浪费,例如浏览器实际才重绘2帧,但却计算了3帧,那么有1帧的计算就浪费了。
成熟做法:
引入requestAnimationFrame,这个方法是用来在页面重绘之前,通知浏览器调用一个指定的函数,以满足开发者操作动画的需求。
这个函数类似setTimeout,只调用一次。
function draw() { requestAnimationFrame(draw); // ... Code for Drawing the Frame ... }
递归调用,就可以实现定时器。
但是,这样完全跟浏览器帧频同步了,无法自定义动画的帧频,是无法满足需求的。
接下来需要考虑如何控制帧频。
简单做法:
var fps = 30; function tick() { setTimeout(function() { requestAnimationFrame(tick); draw(); // ... Code for Drawing the Frame ... }, 1000 / fps); } tick();
这种做法,比较直观的可以发现,每一次setTimeout执行的时候,都还要再等到下一个requestAnimationFrame事件到达,累积下去会造成动画变慢。
自行控制时间跨度:
var fps = 30; var now; var then = Date.now(); var interval = 1000/fps; var delta; function tick() { requestAnimationFrame(tick); now = Date.now(); delta = now - then; if (delta > interval) { // 这里不能简单then=now,否则还会出现上边简单做法的细微时间差问题。例如fps=10,每帧100ms,而现在每16ms(60fps)执行一次draw。16*7=112>100,需要7次才实际绘制一次。这个情况下,实际10帧需要112*10=1120ms>1000ms才绘制完成。 then = now - (delta % interval); draw(); // ... Code for Drawing the Frame ... } } tick();
针对低版本浏览器再优化:
如果浏览器没有requestAnimationFrame函数,实际底层还只能用setTimeout模拟,上边做的都是无用功。那么可以再改进一下。
var fps = 30; var now; var then = Date.now(); var interval = 1000/fps; var delta; window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; function tick() { if(window.requestAnimationFrame) { requestAnimationFrame(tick); now = Date.now(); delta = now - then; if (delta > interval) { // 这里不能简单then=now,否则还会出现上边简单做法的细微时间差问题。例如fps=10,每帧100ms,而现在每16ms(60fps)执行一次draw。16*7=112>100,需要7次才实际绘制一次。这个情况下,实际10帧需要112*10=1120ms>1000ms才绘制完成。 then = now - (delta % interval); draw(); // ... Code for Drawing the Frame ... } } else { setTimeout(tick, interval);
draw(); } } tick();
最后,还可以加上暂停。
var fps = 30; var pause = false; var now; var then = Date.now(); var interval = 1000/fps; var delta; window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; function tick() { if(pause) return; if(window.requestAnimationFrame) { ... } else { ... } } tick();
requestAnimationFrame的用法
// shim layer with setTimeout fallback
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
// usage:
// instead of setInterval(render, 16) ....
(function animloop(){
requestAnimFrame(animloop);
render();
})();
// place the rAF *before* the render() to assure as close to
// 60fps with the setTimeout fallback.
对requestAnimationFrame更牢靠的封装
Opera浏览器的技术师Erik M?ller 把这个函数进行了封装,使得它能更好的兼容各种浏览器。你可以读一读这篇文章,但基本上他的代码就是判断使用4ms
还是16ms
的延迟,来最佳匹配60fps。下面就是这段代码,你可以使用它,但请注意,这段代码里使用的是标准函数,我给它加上了兼容各种浏览器引擎前缀。
(function() {
var lastTime = 0;
var vendors = [‘webkit‘, ‘moz‘];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+‘RequestAnimationFrame‘];
window.cancelAnimationFrame =
window[vendors[x]+‘CancelAnimationFrame‘] || window[vendors[x]+‘CancelRequestAnimationFrame‘];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
我来看看使用requestAnimationFrame的效果
// requestAnim shim layer by Paul Irish window.requestAnimFrame = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(/* function */ callback, /* DOMElement */ element){ window.setTimeout(callback, 1000 / 60); }; })(); // example code from mr doob : http://mrdoob.com/lab/javascript/requestanimationframe/ var canvas, context, toggle; init(); animate(); function init() { canvas = document.createElement( ‘canvas‘ ); canvas.width = 512; canvas.height = 512; context = canvas.getContext( ‘2d‘ ); document.body.appendChild( canvas ); } function animate() { requestAnimFrame( animate ); draw(); } function draw() { var time = new Date().getTime() * 0.002; var x = Math.sin( time ) * 192 + 256; var y = Math.cos( time * 0.9 ) * 192 + 256; toggle = !toggle; context.fillStyle = toggle ? ‘rgb(200,200,20)‘ : ‘rgb(20,20,200)‘; context.beginPath(); context.arc( x, y, 10, 0, Math.PI * 2, true ); context.closePath(); context.fill(); }
requestAnimationFrame API
window.requestAnimationFrame(function(/* time */ time){
// time ~= +new Date // the unix time
});
回调函数里的参数可以传入时间。
各种浏览器对requestAnimationFrame的支持情况
谷歌浏览器,火狐浏览器,IE10+都实现了这个函数,即使你的浏览器很古老,上面的对requestAnimationFrame封装也能让这个方法在IE8/9上不出错。
参考文章:http://www.jb51.net/article/70678.htm
http://www.webhek.com/requestanimationframe/
http://www.cnblogs.com/kenkofox/p/3849067.html
http://blog.csdn.net/qingyafan/article/details/52335753
秀才提笔忘了字:javascript使用requestAnimationFrame实现动画