最近开发项目动效开发愈来愈多 ;
部分动效须要在页面滑动的时候执行必定的效果;
可是发如今移动端 不少时候页面滑动的速度快的时候 , 动效呈现的不稳定性越明显 , 会不流畅; 虽然使用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为浏览器滚动条向上滚动, }
欢迎各位大大交流 , 有更好的脑洞和哪里写的不足的地方欢迎留言讨论!