

优化移动端window.onscroll的执行频率方案
source link: https://zhuanlan.zhihu.com/p/32061260?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

优化移动端window.onscroll的执行频率方案
脑洞爆炸的背景
最近发现在移动端 很多时候页面滑动的速度快的时候 , 动效呈现的不稳定性越明显 , 会不流畅; 虽然使用css3的过渡可以从视觉层面解决这个问题 , 但是并不能根治, 于是乎想到了一个方案。。。
requestAnimationFrame(RAF)
h5新增的用于刷帧的api , 大家可以网上找到很多相关教程 , 用法及其简单 , 跟使用setTimeOut一样; 此api的初衷本人理解为用于更好的执行动画 , 而找到的一句话 “执行渲染下一帧之前的动作”可能更好的帮助你理解这个api;
而之前说的移动端动画不流畅的原因是因为快速滑动的时候 , 两次出发scroll之间的“间距”越来越大,而导致需要根据滑动计算的精度越来越不准 , 我们当然希望每滑动1px执行一次scroll是最完美的啦~(虽然基本不可能)
于是乎 , 想到了一个方案?!
可以在window.scroll开始的时候开启RAF,在window.scroll结束的时候关闭RAF , 所有需要执行在scroll中的函数搬到RAF中执行就好了
事实上实验结果是成功的
每次window.scroll的时候在页面插入一次scroll字样 , 每次raf执行的时候插入raf字样 , 在保证一段scroll过程中只存在唯一一个RAF , 输出如上图
事实证明 ios微信环境下 , raf触发的频率在快速滑动页面的时候确实高于scroll;
唯一的一个实现难点在于 scrollend如何监听
在每一次scroll的时候 , 开启一个50ms的定时器 , 定时器认定为scroll结束 , 但是每次滑动都创建定时器就乱套了 , 所以要在创建定时器之前先清除定时器;
第一次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为浏览器滚动条向上滚动,
}
欢迎各位大大交流 , 有更好的脑洞和哪里写的不足的地方欢迎留言讨论!
</div
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK