virtualScrollThrottle


背景知识之事件循环

详见HTML规范

这里只挑与本文相关的讲

在事件循环中定义了很多任务源,比如鼠标键盘等输入操作的用户交互任务源

一次点击操作,其实包含多个输入操作(mousedown,mouseup,click),都添加到相同的任务源队列中,进而产生多轮的事件循环,其表现就是执行相关元素的事件回调(宏任务)

宏任务执行后,后面就是微任务队列和更新渲染阶段

每轮事件循环可能是非常快的,每秒执行事件循环的次数可能大于60次

受硬件刷新率影响,我们只要保证 fps 达到最大硬件刷新率(比如60)即可,因此不需要每轮事件循环都更新渲染

视图渲染的时机又是什么时候呢?update rendering 发生在本轮事件循环的 microtask 队列被执行完之后,也就是说执行任务的耗时会影响视图渲染的时机。通常浏览器以每秒 60 帧(60fps)的速率刷新页面,据说这个帧率最适合人眼交互,大概16.7ms 渲染一帧,所以如果要让用户觉得顺畅,单个 macrotask 及它相关的所有 microtask 最好能在16.7ms 内完成。但也不是每轮事件循环都会执行视图更新,浏览器有自己的优化策略,例如把几次的视图更新累积到一起重绘,重绘之前会通知 requestAnimationFrame 执行回调函数,也就是说 requestAnimationFrame 回调的执行时机是在一次或多次事件循环的UI render阶段

最后的总结:

1、每轮事件循环分为3个步骤:
a) 执行 macrotask 队列的一个任务

b) 执行完当前 microtask 队列的所有任务

c) UI render

2、浏览器只保证 requestAnimationFrame 的回调在重绘之前执行,没有确定的时间,何时重绘由浏览器决定

输入事件与滚动事件的执行时机

对于输入事件,其执行时机为每轮事件循环的任务执行阶段,这个事件是不受刷新率影响的,每秒的执行次数可能多于60次

为什么谈这个呢,因为这个与滚动事件(scroll)回调的执行时机不一致

按照 HTML 规范,滚动事件回调在 UI Render 阶段的某个步骤中进行,而不是单独的一个任务源

也就是说,滚动事件回调受渲染时机影响,仅执行更新渲染时才执行该回调。

换句话说,该事件自带节流。

举个例子验证下输入事件和更新渲染的执行时机

document.addEventListener("mousemove",function(){
  let start = performance.now()
  console.log("mousemove:",start)
  requestAnimationFrame(function(t){console.log("ui render:",start,t)})
})
// 结果就是可能输出几轮 mousemove 然后执行一次 ui render -- 清空 rAF 回调队列(输出多次 ui render)
/*
mousemove: 4091.025000088848
ui render: 4091.025000088848 4077.594
mousemove: 4098.845000029542
ui render: 4098.845000029542 4094.278
mousemove: 4110.160000040196
mousemove: 4115.5349999899045
ui render: 4110.160000040196 4110.962
ui render: 4115.5349999899045 4110.962
mousemove: 4123.810000019148
mousemove: 4130.160000058822
ui render: 4123.810000019148 4127.719
ui render: 4130.160000058822 4127.719
*/

说明更新渲染有一定的间隔,至少是 1/60 的间隔,而输入任务没有此限制

所以,以下代码是没有效果的,因为该回调已经自带节流了.

document.addEventListener("scroll",function(e){
  requestAnimationFrame(function(t){
    //执行scroll具体逻辑
  })
})

什么时候滚动需要做节流

利用节流可以减少回调的执行次数,使得固定时间周期内只执行一次

刚才提到,滚动事件自带节流,节流的时间周期是与渲染时机相关

判断是否需要额外的节流的关键是:当前的节流规则,是否大部分回调的执行都能让用户受益

如果是动画效果,实时绘制的界面等,则不需要额外的节流了。

以长列表为例,每次执行滚动回调,会计算新的渲染列表项及滚动偏移位置。如果应用更大时间周期的节流,会出现某一帧出现滚动但界面没有更新的情况,让用户感觉产生卡顿。

而其他比较复杂的业务逻辑,不能在短时间内得到反馈的,则需要额外进行节流

以滚动懒加载图片为例

由于每秒的滚动回调的执行次数可能达到60次,而每次执行都需要去获取当前处于视区的占位图并发起图片请求

而这大部分回调的执行,用户是不能受益的,所以我们可以提高节流的时间周期,比如 500ms 这样

结论

滚动事件已自带节流,只有一些特定的业务逻辑才需要额外进行更高时间周期的节流


文章作者: Yuukyou
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Yuukyou !
评论
  目录