在用js寫動畫的時候,無非使用 setTimeout/setInterval 或者 requestAnimationFrame 來處理動畫(在jquery的代碼里也是這么干的),本文主要為了記錄下兩者的區別及使用兩者來實現動過程。
以實現一個簡單的滾動到頂部為例
setInterval
setInterval() 方法重復調用一個函數或執行一個代碼段,在每次調用之間具有固定的時間延遲。返回一個intervalID,可用於 cancelInterval 達到結束循環的效果。
setTimeout 和 setInterval 的實現基本沒區別,一個是定時執行,一個是定時循環執行,前者加個自己調用自己就是后者了,下面主要以 setInterval 為代表
實現過程:
1.寫個方法,該方法需要傳入一個代表動畫所需執行的時間的參數(如:滾動到頂部需要1000毫秒)
function doAnimate(duration){ return function(){ // do something } }
2.取當前頁面距頂部高度、滾動速度(以勻速為例)、寫個開始動畫的函數(為了給addEventListener綁事件傳參,其實也可直接 dom.onclick = fn )
function doAnimate(duration){ return function(){ var start = document.documentElement.scrollTop; var scrollSpeed = start/duration*(1000/60); // 以大多瀏覽器的刷新頻率60幀(60Hz)為准 1秒60次的刷新 var timer; var startTime = +new Date(); // 標記時間,僅供后面測效果用而已 function startAnimate(){ timer = setInterval(function () { // do something },1000/60) } } }
3.寫滾動動畫
function doAnimate(duration){ return function(){ var start = document.documentElement.scrollTop; var scrollSpeed = start/duration*(1000/60); // 這里取到平均滾動距離,以大多瀏覽器的刷新頻率60幀(60Hz)為准,1秒60次的刷新 var timer; function startAnimate(){ timer = setInterval(function () { document.documentElement.scrollTop = start < scrollSpeed ? start -= start : start -= scrollSpeed; // 當前該滾動到何處,如果距離頂部小於平均滾動距離,直接滾到scrollTop為0;如果大於,則取到的觸發時高度以平均滾動距離遞減 if(start === 0){ clearInterval(timer); } },1000/60) } } }
4.寫個很高的頁面、給個div、加個click事件觸發滾動回頂部
html
<!-- 為了更好的看到滾動效果及測滾動是否平滑,我們用某度的大圖扔頁面上 --> <img src="./來自百度壁紙的大圖" /> <img src="./來自百度壁紙的大圖" /> <img src="./來自百度壁紙的大圖" /> <div id="scrollTop_1000" style="width: 60px;height: 20px;background: #fff;position: fixed; bottom:50px;right:50px;text-align: center">1000</div> <div id="scrollTop_3000" style="width: 60px;height: 20px;background: #fff;position: fixed; bottom:80px;right:50px;text-align: center">3000</div>
js
function doAnimate(duration){ return function(){ var start = document.documentElement.scrollTop; var scrollSpeed = start/duration*(1000/60); // 這里取到平均滾動距離,以大多瀏覽器的刷新頻率60幀(60Hz)為准,1秒60次的刷新 var timer; function startAnimate(){ timer = setInterval(function () { document.documentElement.scrollTop = start < scrollSpeed ? start -= start : start -= scrollSpeed; // 當前該滾動到何處,如果距離頂部小於平均滾動距離,直接滾到scrollTop為0;如果大於,則取到的觸發時高度以平均滾動距離遞減 if(start === 0){ clearInterval(timer); } },1000/60) } } } document.getElementById('scrollTop_1000').addEventListener('click',doAnimate(1000),!1) document.getElementById('scrollTop_3000').addEventListener('click',doAnimate(3000),!1)
效果如圖:
截圖分別測了設置 duration 為1000和3000的滾動效果
requestAnimationFrame
requestAnimationFrame() 方法告訴瀏覽器您希望執行動畫,並請求瀏覽器調用指定的函數在下一次重繪之前更新動畫。該方法將在重繪之前調用的回調作為參數。返回一個 requestID ,可用於 cancelAnimationFrame 達到取消 requestAnimationFrame 動畫的效果。
實現思路如上,代碼如下:
html
<!-- 為了更好的看到滾動效果及測滾動是否平滑,我們用某度的大圖扔頁面上 --> <img src="./來自百度壁紙的大圖" /> <img src="./來自百度壁紙的大圖" /> <img src="./來自百度壁紙的大圖" /> <div id="scrollTop_1000" style="width: 60px;height: 20px;background: #fff;position: fixed; bottom:50px;right:50px;text-align: center">1000</div> <div id="scrollTop_3000" style="width: 60px;height: 20px;background: #fff;position: fixed; bottom:80px;right:50px;text-align: center">3000</div>
js
function doAnimate(duration){ return function(){ var start = document.documentElement.scrollTop; // 獲取實時時間 var nowTime = function(){ return +new Date } // 開始時間 用於計算動畫運行時間和動畫規定時間的百分比 var startTime = nowTime(); var animateId; var startAnimate = function() { animateId = requestAnimationFrame(toTop); } var stopAnimate = function() { cancelAnimationFrame(animateId) } function toTop(){ // 剩下時間 var restTime = Math.max(0, duration - ( nowTime() - startTime)) var percent = restTime / duration || 0; var changeStyle = function(value){ document.documentElement.scrollTop = value; } // 根本比例獲取剩下的距離,也就是實時距離頂部的距離 var distance = start * percent; if(!distance){ changeStyle(distance) stopAnimate(); }else{ changeStyle(distance) startAnimate(); } } startAnimate(); } } document.getElementById('scrollTop_1000').addEventListener('click',doAnimate(1000),!1) document.getElementById('scrollTop_3000').addEventListener('click',doAnimate(3000),!1)
效果如圖:
沒區別,沒毛病,然而並沒有和上面用同一張圖...(其實打印下時間,會發現 setInterval 會是1000毫秒以內,大致在960-980毫秒之間,這個梗哪位大神可知???求解!!!)
兩者的區別
requestAnimationFrame 會請求瀏覽器調用指定的函數在下一次重繪之前更新動畫,所以開發者不用考慮頻率/丟幀問題
setInterval 中,會因為瀏覽器顯示頻率和 JavaScript 單線程可能會引發阻塞的問題而導致丟幀(視覺應為動畫不流暢)
requestAnimationFrame 會把每一幀中的所有 DOM 操作集中起來,在一次重繪或回流中就完成,性能方面更出色
對於隱藏或者不可見的元素,requestAnimationFrame 將不會進行重繪或回流,這點可減少cpu,gpu及內存的負荷
setInterval 兼容一些老版本的瀏覽器(jquery保留這個應該也是為了兼容老版本瀏覽器...)
requestAnimationFrame 兼容圖
順便扔上jquery里animate的部分代碼:
jQuery.fx.start = function() { if ( !timerId ) { timerId = window.requestAnimationFrame ? window.requestAnimationFrame( raf ) : window.setInterval( jQuery.fx.tick, jQuery.fx.interval ); } }; jQuery.fx.stop = function() { if ( window.cancelAnimationFrame ) { window.cancelAnimationFrame( timerId ); } else { window.clearInterval( timerId ); } timerId = null; };
略顯尷尬... 在我windows上和mac上也保留一些老版本的瀏覽器,測效果的結果簡直蛋疼...看來兼容方面還是需要做處理的,天將降大任於 setInterval 啊 :-D
歡迎交流 歡迎指出各個問題~