秀才提笔忘了字:javascript使用requestAnimationFrame实现动画

Posted 最骚的就是你

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了秀才提笔忘了字: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上不出错。

 

其实,使用setInterval或setTimeout来实现主循环,根本错误就在于它们抽象等级不符合要求。我们想让浏览器执行的是一套可以控制各种细节的api,实现如“最优帧速率”、“选择绘制下一帧的最佳时机”等功能。但是如果使用它们的话,这些具体的细节就必须由开发者自己来完成。

 

requestAnimationFrame不需要使用者指定循环间隔时间,浏览器会基于当前页面是否可见、CPU的负荷情况等来自行决定最佳的帧速率,从而更合理地使用CPU。

参考文章: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实现动画的主要内容,如果未能解决你的问题,请参考以下文章

酒色财气诗

java基础——既然有了字节流,为什么还要有字符流呢?

APP推荐专业治疗提笔忘字 -- Google 手写输入

读书人的最高荣誉(明朝那些事儿)

开篇提笔的叙话

九月,提笔反思