V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
autoxbc
V2EX  ›  浏览器

基于 Opera 12 实现的系统级 scroll 事件防抖

  •  
  •   autoxbc · 2017-08-05 07:58:06 +08:00 · 2835 次点击
    这是一个创建于 2668 天前的主题,其中的信息可能已经有所发展或是发生改变。

    前情提要: 在 为何 iOS 的 scroll 阻塞 dom 机制没被大规模借鉴? 中提到,iOS 的 UIWebView 有个特性,页面滚动时会阻塞 js 的 scroll 事件触发和 css 的页面重绘,大大提高了滚动性能。本质上说,这是在前端开发者之外,在浏览器上实现的 scroll 防抖优化,哪怕程序员并不需要(破坏一致性)。

    作为用户我是需要的,因为有些人写的代码实在不靠谱。我要在桌面浏览器上实现同样的功能,即对那些包含糟糕滚动性能的页面,统一加上 scroll 防抖处理:对一连串 scroll 动作,只在最后触发一个 scroll 事件,哪怕开发者没做任何防抖和节流。

    在 Opera 12 浏览器中,存在一个全局 Opera 对象,提供一些堪称魔法的函数,让用户对网页事件流做细致的调整,举个例子:
    有些网页会监听用户的 copy 动作,显示一个难看的提示框,我们这么处理

    opera.addEventListener('BeforeEventListener.copy',function(e){
        e.preventDefault();
    });
    

    哪怕不知道回调函数,对 copy 事件的监听也被去除了,这个方法是本文的核心。

    为了观察 scroll 是否完全停止,用一个全局变量保存 scroll 事件的时间戳。

    window.lastScroll = new Date();
    

    为了即使自己的其他代码禁用 scroll 事件,也可以正确的记录时间戳,同样使用 Opera 对象

    opera.addEventListener('AfterEvent.scroll', function (e){
        window.lastScroll = e.timeStamp ;
    });
    

    即使 scroll 事件不触发,Opera 也可以捕获 AfterEvent.scroll 事件,时间戳是一样的。

    下面对 scroll 事件做防抖,不要介意我的渣代码

    opera.addEventListener('BeforeEventListener.scroll',function(e){
        // 阻止事件触发,禁用原有的回调
        e.preventDefault();
    
        var callee = arguments.callee ;
        // timer 为事件墙,只让第一个 scroll 事件通过,其他废弃
        if( callee.timer )
            return;
    
        // 首次滚动立即延迟,观察有无后续滚动
        // 同时记录时间戳
        callee.timer = setTimeout( function(){
            // 高于阈值,滚动完全结束,进入回调组装流程
            if( new Date() - window.lastScroll > 800 )
            {
                // 清理事件墙
                callee.timer = null ;
                // 提取原来的回调函数 e.listener
                // 提取原来的 scroll 事件 e.event
                // 组合并触发回调
                e.listener(e.event);
            } else {
                // 低于阈值,滚动未结束,继续延迟回调
                // 同时更新时间戳
                callee.timer = setTimeout( arguments.callee , 400 );
            }
        } , 400 );
    });
    

    这段代码的逻辑是,从开发者对 scroll 的监听过程捕获 BeforeEventListener.scroll 事件,提取其中的 scroll 事件( e.event ),回调函数( e.listener ),按照一定的逻辑重新组装到一起,或者立即触发,或者延迟执行。

    在延迟的过程中,对一串 scroll 事件,只在滚动停止后触发一次。

    至此,借助浏览器提供的专有对象,实现了 scroll 事件的系统级防抖处理,滚动体验被大大提升。在我有限的认知里,没有其他浏览器有这么强的内置功能,可以实现类似的效果。如果 Chrome 或者 Firefox 能够做到,请留言告诉我。

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3000 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 12:54 · PVG 20:54 · LAX 04:54 · JFK 07:54
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.