浏览器渲染
概述
浏览器有多个进程,其中渲染进程是最为熟悉的,渲染进程中的主线程是另一个重要的线程,有 js 代码的执行,DOM 事件处理,HTML、CSS 解析等任务,还要根据 DOM 和 CSSOM 生成对应的渲染树,布局树Layout tree,层叠信息,以及需要给合成线程的层树 Layer Tree(这个树是合成线程要光栅化处理的数据结构)、渲染指令,之后交给 合成线程 Compositor Thread 合成操作
合成要将前面的层树再分块,把块交给 光栅线程进行光栅化处理,处理后的图块再交给合成线程去生成帧
帧经过 IPC 交给浏览器进程,浏览器进程会给 GPU 发送帧数据,GPU 负责处理显示
下面图片也是渲染管线的处理流程
合成线程
合成线程和主线程是相互独立的,合成线程接收从主线程解析处理后的层树Layer Tree。页面会根据生成的层树对每一层进行单独的光栅化,也可以使用 will-changeCSS 属性控制元素强制为一层
会优先处理视口相关的层,如果一层过大,合成线程还会对层进行分块处理,以便加快速度
合成线程中的事件处理
不可合成标记(Non-fast Scrollable Region)
绑定了事件的元素,都会被标记。标记了的元素,合成线程会等待,不会直接合成处理,要将元素事件发送给主线程去处理
不被标记的元素合成线程就会合成,不会受到影响
主线程
当在浏览器地址栏输入url后都发生了什么
上面的链接中有一部分不是主线程(渲染进程)的任务,只有从接收了网络流后,开始解析资源开始,直到合成成合成线程需要的数据为止
主线程要处理从合成线程来的标记的事件,因为只有这个线程是 JS 执行的线程
所以有两种情况会阻塞页面渲染(动画)
- 绑定事件,绑定到了根元素上,这样整个页面都会被标记,合成线程就无法合成,要等待事件完成
- 布局树的改变(重排、回流 reflow),会让主线程再走一遍渲染流程
让事件不影响合成的属性
// passive: true |
光栅线程
光栅化:将合成线程输出的数据变成屏幕上显示的像素的过程
事件传递
浏览器进程会响应用户的鼠标移动,触摸等事件,并且会把这些信息传递给 IPC,之后给渲染进程。渲染进程中的合成线程会去查看这些事件的标记,标记的事件会传送给主线程去做处理
由虚拟列表引发的浏览器渲染原理的思考
虚拟列表(只渲染视口内的DOM,不重新渲染视口外的DOM,对于大数据列表有渲染优势)
虚拟列表带来的问题
虚拟列表的特性就是只渲染视口内的DOM,所以,当用户下拉滚动条过快时,就会出现“列表白屏”或者“抖动”的现象
这里会有人说加:“缓冲区”,比如让列表多渲染点屏外DOM,确实,能稍微解决一点问题,能让你稍微的拖动滚动条的时候快一点,但是你“猛”拖滚动条还是会有一样的问题
这里要分析的不是“缓冲区”的问题,而是为什么会白屏,真正原因是什么
项目背景
vue3 + antd vue的table组件
核心代码逻辑如下,虚拟列表的逻辑不再赘述,简单说:用一个高度极大的 div撑开显示滚动条,之后计算滚动条的 scrollTop在大列表中所有列表行合起来的高度的位置,之后只显示大列表中从这个“位置”开始的行到屏幕底边缘
template的代码如下
根据上文的浏览器核心原理得知,滚动时的事件,是界面线程通过IPC给合成线程的,合成线程又给我们的主线程(我们主线程的js代码中的事件监听)
问题在于,触发这个监听事件的频率和我们拖动滚动条的体感的频率不一致。比如,你飞快的拖动滚动条,但浏览器触发这个监听回调还是按照 16.3ms来触发的(一秒60次),实际上,我们体感的应该更快,比如每秒100次触发可能才能跟上我们的速度
会导致什么呢?白屏,你在一秒中拖动的位置早已超出一秒钟最后一帧计算出的位置,所以浏览器追不上你了
这怎么解决能让体验好一点呢,其实也没有太好的办法,但是到可以优化一下前面快速拖动中的那些“没用的更新”,可以用防抖来做,快速拖动的触发回调会被下一次回调在我们设定的时间周期内顶掉,不会触发,仅仅能做到不做过多的渲染,但精确控制当用户停下时的列表渲染目前还没有好的方法



