最近開發項目動效開發愈來愈多 ;
部分動效須要在頁面滑動的時候執行必定的效果;
可是發如今移動端 不少時候頁面滑動的速度快的時候 , 動效呈現的不穩定性越明顯 , 會不流暢; 雖然使用css3的過渡能夠從視覺層面解決這個問題 , 可是並不能根治, 因而乎想到了一個方案。。。css
h5新增的用於刷幀的api , 你們能夠網上找到不少相關教程 , 用法及其簡單 , 跟使用setTimeOut同樣; 此api的初衷本人理解爲用於更好的執行動畫 , 而找到的一句話 「執行渲染下一幀以前的動做」可能更好的幫助你理解這個api;ios
而以前說的移動端動畫不流暢的緣由是由於快速滑動的時候 , 兩次出發scroll之間的「間距」愈來愈大,而致使須要根據滑動計算的精度愈來愈不許 , 咱們固然但願每滑動1px執行一次scroll是最完美的啦~(雖然基本不可能)css3
因而乎 , 想到了一個方案?!web
能夠在window.scroll開始的時候開啓RAF,在window.scroll結束的時候關閉RAF , 全部須要執行在scroll中的函數搬到RAF中執行就行了 api
事實上實驗結果是成功的瀏覽器
每次window.scroll的時候在頁面插入一次scroll字樣 , 每次raf執行的時候插入raf字樣 , 在保證一段scroll過程當中只存在惟一一個RAF , 輸出如上圖微信
事實證實 ios微信環境下 , raf觸發的頻率在快速滑動頁面的時候確實高於scroll;函數
惟一的一個實現難點在於 scrollend如何監聽動畫
在每一次scroll的時候 , 開啓一個50ms的定時器 , 定時器認定爲scroll結束 , 可是每次滑動都建立定時器就亂套了 , 因此要在建立定時器以前先清除定時器;this
捋一下:
第一次scroll, 清除一個不存在的定時器 , 而後建立定時器 , 50ms以後執行的就是scroll結束
第二次scroll , 清除第一次建立的定時器 , 建立一個定時器 , 50ms以後執行的就是scroll結束
。。。。
最後一次scroll , 清除倒數第二次建立的定時器 , 建立一個定時器 , 因爲沒有下一次scroll了 , 那麼這個定時器就真的是最後一次scroll了
因而經過這樣的方案迂迴造成了scrollEnd , 雖然有50ms的偏差~
而後代碼以下 :
var rAF = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) { window.setTimeout(callback, 1000 / 60); }; var cancelRAF = window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.webkitCancelRequestAnimationFrame || window.mozCancelRequestAnimationFrame || window.oCancelRequestAnimationFrame || window.msCancelRequestAnimationFrame || clearTimeout; class BetterScroll { constructor() { let sy = window.scrollY; this.onScroll = this.onScroll; this.onScrollEnd = this.onScrollEnd; this.scrollList = []; this.scrollEndList = []; this.scrollTimer = null; this.nowWsy = sy; this.lastY = sy; this.direction = 0; this.rafMark = null; this.rafingMark = false; this.gap = 0; this.bindEvent(); } onScroll(cb) { if (typeof cb !== 'function') { return; } this.scrollList.push(cb); } onScrollEnd(cb) { if (typeof cb !== 'function') { return; } this.scrollEndList.push(cb); } scrollEnd() { let winInfo = { sy : this.nowWsy, gap : Math.abs(this.gap), dir : this.direction, } for (let i = 0, len = this.scrollEndList.length; i < len; i++) { try { this.scrollEndList[i](winInfo); } catch (error) { console.warn(error) } } } rafing() { this.nowWsy = window.scrollY; this.gap = this.nowWsy - this.lastY; // 1爲向上滑動 -1 爲向下滑動 !!this.gap && (this.direction = (((this.gap >= 0) | 0 ) - 0.5) * 2); this.lastY = this.nowWsy; let winInfo = { sy : this.nowWsy, //當前window的scrollY gap : Math.abs(this.gap), //上次到此次滑動的距離 dir : this.direction, // 滑動方向 } for (let i = 0, len = this.scrollList.length; i < len; i++) { try { this.scrollList[i](winInfo); } catch (error) { console.warn(error) } } this.startRaf(); } startRaf() { let _this = this; this.rafMark = rAF(function () { _this.rafing(); }) } bindEvent() { let _this = this; window.addEventListener('scroll', function () { clearTimeout(_this.scrollTimer); if (!_this.rafingMark) { _this.startRaf(); _this.rafingMark = true; } _this.scrollTimer = setTimeout(function () { cancelRAF(_this.rafMark); _this.scrollEnd(); _this.rafingMark = false; }, 50); }, 0) } } let btScroll = new BetterScroll(); export default btScroll;
用法 :
組建拋出btScroll對象 ,
提供兩個方法
btScroll.onScroll(callback); window正在scrolling的函數 , 回調函數接受參數 winInfo btScroll.onScrollEnd(callback); window滑動結束的函數 , 回調函數接受參數 winInfo winInfo : { sy : window的scrollY值, gap : 上一次scroll到這一次scroll之間的差值絕對值, dir : window的滑動方向 1爲瀏覽器滾動條向下滾動 , -1爲瀏覽器滾動條向上滾動, }
歡迎各位大大交流 , 有更好的腦洞和哪裏寫的不足的地方歡迎留言討論!