• 秀才提笔忘了字: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 {
    				 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

  • 相关阅读:
    省选模拟27
    省选模拟26
    省选模拟25
    省选模拟23
    PHP 各个框架的优缺点(超详细)
    windows 下安装中文版Git
    支付宝APP支付里设置应用网关和授权回调地址是不必填的
    linux服务器下安装phpstudy 如何命令行进入mysql
    js金钱转大写
    多个 (li) 标签如何获取获取选中的里面的某个特定值??
  • 原文地址:https://www.cnblogs.com/libin-1/p/6096067.html
Copyright © 2020-2023  润新知