优化移动端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为浏览器滚动条向上滚动,
}

欢迎各位大大交流 , 有更好的脑洞和哪里写的不足的地方欢迎留言讨论!

原文地址:https://www.cnblogs.com/jlfw/p/12187472.html

时间: 2024-08-07 13:11:01

优化移动端window.onscroll的执行频率方案的相关文章

window.onscroll

---恢复内容开始--- 今天在学习javascript的过程中被onscroll这个东西堵了一下午.心情极度郁闷. 在高度较大的网页中,我们通常会加一个返回顶部的按钮,方便用户操作. 代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html xmlns="http://www.w3.org/

关于javascript的window.onscroll方法

---恢复内容开始--- 今天在学习javascript的过程中被onscroll这个东西堵了一下午.心情极度郁闷. 在高度较大的网页中,我们通常会加一个返回顶部的按钮,方便用户操作. 代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html xmlns="http://www.w3.org/

使用限制函数执行频率的函数代理

使用代理限制函数的调用频率 假设一个经典的CURD页面上,要做一个Ajax异步查询功能. 放一个查询按钮,点击查询,系统会到远程服务端请求数据,一秒之后返回查询结果. 很快,功能实现了! 但假如用户一秒内点击了三次查询,会发生什么? 为了解决这个问题,我们可能会在用户点击查询之后禁用查询按钮,或者在处理查询时上锁,返回结果后再把锁放开. 很好,做到这里,已足够日常使用. 这里只解决了一个问题:按钮的点击.而输入框的输入.选择框的变化.鼠标的移动.滚轮的滚动,这些事件触发频率高的问题怎么解决? 为

IE attachEvent事件处理程序(事件绑定的函数)的this指向的是window不是执行当前事件的dom元素

IE attachEvent事件处理程序(事件绑定的函数)的this指向的是window不是执行当前事件的dom元素. attachEvent(type,listener); listener函数中的this不是指向执行当前事件的dom而是window切记,感觉这一点IE做的太奇怪了!

利用DBMS_STATS包修改统计信息,欺骗优化器,生成糟糕的执行计划

在使用基于成本的优化器的优化器时,优化器生产执行计划时要估算每条SQL的执行成本,选择最佳的执行计划来执行sql语句.通过操纵统计信息就可以简介操纵执行计划的生成. 当然 需要强调的一点是,这是非常危险的行为 1 创建测试表 SQL> create table test_stats  as   2  select * from dba_objects ; Table created. 2 收集统计信息 SQL> EXEC dbms_stats.gather_table_stats(ownnam

window.onscroll事件 谷歌兼容性问题

直接贴代码 window.onscroll=function(){ var scroHeight=document.documentElement.scrollTop||document.body.scrollTop; if(scroHeight >= 119 ){ document.getElementById("cd").className="table1"; }else{ document.getElementById("cd").c

js的window.onscroll事件兼容各大浏览器

为窗口添加滚动条事件其实非常的简单, window.onscroll=function(){}; 注意在获取滚动条距离的时候 谷歌不识别document.documentElement.scrollTop,必须要加上document.body.scrollTop:即 var scrolltop=document.documentElement.scrollTop||document.body.scrollTop; 这样才能兼容各个浏览器! <!DOCTYPE html PUBLIC "-/

[MySQL优化] -- 如何了解SQL的执行频率

MySQL 客户端连接成功后,通过 show [session|global]status 命令 可以提供服务器状态信息,也可以在操作系统上使用 mysqladmin extended-status 命令获得这些消息.     show [session|global] status 可以根据需要加上参数“ session ”或者“ global ”来显示 session 级(当前连接)的统计结果和 global 级(自数据库上次启动至今)的统计结果.如果不写,默认使用参数是“ session

查找存储过程的执行频率和时间

由于公司最近的数据库服务器CPU非常不稳定. 于是乎下手查找问题的来源.想了下,只能从存储过程的执行状态中下手. 查了下资料,发现MSSQL中的系统表sys.dm_exec_procedure_stats会记录存储过程的执行状态数据.字段含义就不累述了.开始干活: 1.将数据插入一张新表记录 select convert(nvarchar(10),getdate(),121) as countdate,d.* into procedure_stats_daily  from SYS.proced