使用Passive event保证平滑滚动

Problem

我们先来看一个Demo, 在 Demo 中,在touchStart的事件的callback中跑了一段JS的循环,打开Chromedevtools,切换到移动端的模式,在页面上滑动的时候,可以明显感觉到左边盒子的滚动有明显的延迟和卡顿,而右边的盒子滚动起来却很顺滑;这两个盒子的touchStart事件的callback跑的循环是一样的,差异在于右边盒子将passive置为了true

Overview

页面的平滑滚动对于用户体验来说是很重要的,特别是移动端,如果开发人员在touchstart或者touchmove中执行了涉及JS的代码,会影响页面的滚动,原因在于页面的滚动在浏览器中是由另一个线程负责的,在页面滚动的过程中,如果触发了JS的执行,这个线程会等待主线程执行完JS再触发滚动(因为在事件回调中可能执行preventDefault,会将滚动行为停止掉),所以,类似于上面demo的代码,左边盒子的touchstartcallback执行了耗时的JS计算,页面的滚动在等待JS的执行完成,用户体验上就会感到卡顿。

const test = document.getElementById('test')
const test1 = document.getElementById('test1')
test.addEventListener('touchstart', e => {
  for (let i = 0; i < 10000000; i += 1) {
    for (let j = 0; j < 100; j += 1) {}
  }
})

test1.addEventListener(
  'touchstart',
  e => {
    let sum = 0
    for (let i = 0; i < 10000000; i += 1) {
      for (let j = 0; j < 100; j += 1) {}
    }
  },
  { passive: true }
)

Solution

在 DOM 的事件绑定中,我们使用addEventListenr这个函数签名的第二个参数是一个obj,我们常用到的就是{capture: true},用于在事件的捕获阶段(从文档的顶层到触发的 dom)绑定事件处理函数,这里还存在一个参数{passive: true},将这个参数置为true之后,相当于通知浏览器,我这个回调函数不会执行preventDefault,对于上面我们demo中看到的滚动卡顿的问题来说,就相当于告诉控制滚动的线程,你继续滚动,我不会打断正常的滚动,所以就产生了demo中的差异。

Reference


兴趣遍地都是,坚持和持之以恒才是稀缺的


Written by@wang yao
I explain with words and code.

GitHub