爲何要說它,源於看到的一道面試題:問題是用js實現一個無限循環的動畫。html
首先想到的是定時器前端
<!doctype html> <html lang="en"> <head> <title>Document</title> <style> #e{ width: 100px; height: 100px; background: red; position: absolute; left: 0; top: 0; zoom: 1; } </style> </head> <body> <div id="e"></div> <script> var e = document.getElementById("e"); var flag = true; var left = 0; function render() { if(flag == true){ if(left>=100){ flag = false } e.style.left = ` ${left++}px` }else{ if(left<=0){ flag = true } e.style.left = ` ${left--}px` } } setInterval(function(){ render() },1000/60) </script> </body> </html>
能夠說是完美實現!面試
至於時間間隔爲何是1000/60,這是由於大多數屏幕渲染的時間間隔是每秒60幀。api
既然setInterval能夠搞定爲啥還要用requestAnimationFrame呢?最直觀的感受就是,添加api的人是個大神級牛人,我只能懷疑本身。瀏覽器
因此搜索相關問題發現如下兩點oop
一、requestAnimationFrame 會把每一幀中的全部DOM操做集中起來,在一次重繪或迴流中就完成,而且重繪或迴流的時間間隔牢牢跟隨瀏覽器的刷新頻率,通常來講,這個頻率爲每秒60幀。
二、在隱藏或不可見的元素中,requestAnimationFrame將不會進行重繪或迴流,這固然就意味着更少的的cpu,gpu和內存使用量。測試
直接上代碼:動畫
<!doctype html> <html lang="en"> <head> <title>Document</title> <style> #e{ width: 100px; height: 100px; background: red; position: absolute; left: 0; top: 0; zoom: 1; } </style> </head> <body> <div id="e"></div> <script> var e = document.getElementById("e"); var flag = true; var left = 0; function render() { if(flag == true){ if(left>=100){ flag = false } e.style.left = ` ${left++}px` }else{ if(left<=0){ flag = true } e.style.left = ` ${left--}px` } } //requestAnimationFrame效果 (function animloop() { render(); window.requestAnimationFrame(animloop); })(); </script> </body> </html>
我沒有添加各個瀏覽器的兼容寫法,這裏只說用法。spa
效果是實現了,不過我想到兩個問題。3d
一、怎麼中止requestAnimationFrame?是否有相似clearInterval這樣的相似方法?
第一個問題:答案是肯定的 必須有:cancelAnimationFrame()接收一個參數 requestAnimationFrame默認返回一個id,cancelAnimationFrame只須要傳入這個id就能夠中止了。
<!doctype html> <html lang="en"> <head> <title>Document</title> <style> #e{ width: 100px; height: 100px; background: red; position: absolute; left: 0; top: 0; zoom: 1; } </style> </head> <body> <div id="e"></div> <script> var e = document.getElementById("e"); var flag = true; var left = 0; var rafId = null function render() { if(flag == true){ if(left>=100){ flag = false } e.style.left = ` ${left++}px` }else{ if(left<=0){ flag = true } e.style.left = ` ${left--}px` } } //requestAnimationFrame效果 (function animloop(time) { console.log(time,Date.now()) render(); rafId = requestAnimationFrame(animloop); //若是left等於50 中止動畫 if(left == 50){ cancelAnimationFrame(rafId) } })(); //setInterval效果 // setInterval(function(){ // render() // },1000/60) </script> </body> </html>
附上一個效果圖。也可直接capy代碼測試。
二、若是我想動畫頻率下降怎麼作,爲何不考慮加快呵呵 當前刷新頻率已是屏幕的刷新頻率了再快也沒有意義了
這個略微麻煩點
默認狀況下,requestAnimationFrame執行頻率是1000/60,大概是16ms多執一次。
若是咱們想每50ms執行一次怎麼辦呢?
requestAnimationFrame執行條件相似遞歸調用 (說的是相似)別咬我,既然這樣的話咱們可否自定一個時間間隔再執行呢?固然定時器這麼low的東西咱們就不考慮了,都已經拋棄它用rAF了(都快結束了我纔想起寫簡寫太他媽長了),
這個思路來源於我幾年前搞IM的一個項目,服務端推送消息爲了減少包的大小不給時間戳,這個咱們作前端的都知道,咱們雖然很牛逼 不過用戶更牛逼,萬一改了時間就很差玩了。
解決方案是 當和服務端通訊時 記錄下一個時間差,(時間差等於服務端時間-本地時間)無論正負咱們只要這個時間差。這樣每當咱們接受到消息 或者發送消息的時候咱們就拿本地時間和是價差相加。這樣就能夠保證和服務端時間是一致的了,思路是否是很牛逼哈哈。
撤了半天咱們經過以上思路來解決下rAF改變間隔的問題
上代碼
<!doctype html> <html lang="en"> <head> <title>Document</title> <style> #e{ width: 100px; height: 100px; background: red; position: absolute; left: 0; top: 0; zoom: 1; } </style> </head> <body> <div id="e"></div> <script> var e = document.getElementById("e"); var flag = true; var left = 0; //當前執行時間 var nowTime = 0; //記錄每次動畫執行結束的時間 var lastTime = Date.now(); //咱們本身定義的動畫時間差值 var diffTime = 40; function render() { if(flag == true){ if(left>=100){ flag = false } e.style.left = ` ${left++}px` }else{ if(left<=0){ flag = true } e.style.left = ` ${left--}px` } } //requestAnimationFrame效果 (function animloop() { //記錄當前時間 nowTime = Date.now() // 當前時間-上次執行時間若是大於diffTime,那麼執行動畫,並更新上次執行時間 if(nowTime-lastTime > diffTime){ lastTime = nowTime render(); } requestAnimationFrame(animloop); })() </script> </body> </html>
附上一個效果:
到此結束了。歡迎吐槽!