秀才提筆忘了字: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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM