浏览器的绘制渲染过程可以总结为 一帧数据的渲染
注意:浏览器并不是因为页面的重绘和回流才会进行数据帧的渲染,一般情况下,浏览器每隔 16ms
刷新一次数据帧。
![](https://img.haomeiwen.com/i5131519/29250c565ec34598.png)
-
GPU Process
GPU
进程,负责绘制(包括重绘和回流)。WebGL
是JS
面向GPU
编程的接口。-
GPU Thread
GPU
主线程。
-
-
Renderer Process
渲染进程,又分为三个线程:-
Compositor Thread
编排/排版线程,负责启动主线程; -
Compositor Tile Worker(s)
栅格化线程,简单理解为 把CPU
数据处理成GUP
数据; -
Main Thread
主线程。
-
浏览器接收到一帧数据Frame start
,由编排线程决定是否启动主线程,由主线程负责执行这一套渲染的逻辑,最终交给 GPU进程 绘制到页面上。
如果一帧数据不需要经过诸如【页面的重绘重排、JS计算、JS事件、CSS动画...】等等的逻辑相关的处理,编排线程是不会启动主线程的,而是直接会绘制到页面上,如
<input type="text" />
上的光标闪烁,渲染完成之后的页面滚动操作。对于这些不经过渲染主进程的操作,自然也不会产生阻塞。
以<input type="text" />
为例,在输入数据时,如果没有注册任何JS事件,那么也就不会涉及逻辑相关的操作,即不会启动主线程。如果注册了事件,原生交互操作会直接提交给GPU进程去绘制,然后会启动渲染主线程。
启动渲染主线程,依次可分为一下几个阶段:
-
input event handlers
首先处理用户输入/设定的各种操作。 -
requestAnimationFrame
它也是一个暴露给开发者的接口,通常用于执行动画,但此刻执行的是上一帧注册的(动画)事件! -
Parse HTML
解析HTML、CSS、JS
,最终生成DOM Tree
如果JS
中调用了requestAnimationFrame()
,此时只会注册该事件,放在下一帧执行。之所以不立即执行,且在页面解析之前再执行上一帧设置的事件,是为了避免(动画)事件的执行导致页面反复的重排重绘。当然,也正是因为每一帧的图像不同,才有了一个动画交互的效果。
解析HTML
的过程中,遇到JS
就调用(交给)V8
引擎处理,所以V8
引擎和JS
的执行是在 渲染主线程 中完成的。另外JS
可能会操作DOM
、修改CSS
,从而导致页面重排,所以JS
的执行是同步的,即 会阻塞页面渲染! -
Recalc Styles
重新计算样式,生成CSS Tree
-
Layout
合并DOM Tree
和CSS Tree
,生成Layer Tree
当然,Layer Tree
与DOM Tree
不一定是一一对应的,比如对某个DOM
元素设置了隐藏,使用CSS
伪元素。 -
Update Layer Tree
更新Layer Tree
,生成 层叠上下文 -
Paint
遍历层级,计算层叠上下文,形成一个层级记录表,记录图层绘制的先后顺序。 -
Composite
根据Layer Tree
和层级记录表,把页面处理成多个图块,并把数据提交给排版线程。
早期浏览器每次只展示视口可见的页面,滚动时又重新渲染可见的部分;后期做了优化,把整个页面拆分成多个图块,视口需要展示哪部分页面,则把涉及的图块拼接到一起(合成)。
在之后的渲染中(如下拉滚动条),如果当前视口需要展示的图块都没有涉及逻辑相关的处理,那么就不会再启动渲染主线程,而是由 排版线程 合成缓存中的这些图块,并提交到数据栅格化阶段。 -
Raster Scheduled、Rasterize
排版线程组织页面图块,栅格化数据,提交给GPU
进程去绘制;
一个数据帧渲染结束,把栅格化的数据提交给GPU
进程 去绘制页面。 -
requestIdleCallback
它也是一个暴露给开发者的接口,在一个数据帧渲染完成并提交之后,如果还有剩余时间,才会处理此API
设置的事件。
MDN:
window.requestIdleCallback()
在浏览器的空闲时段内调用的函数排队。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先进先调用的顺序执行。然而,如果回调函数指定了执行超时时间,则有可能为了在超时前执行函数而打乱执行顺序。
综上所述,在一帧数据的渲染过程中,JavaScript
执行的时机有三处,分别时:requestAnimationFrame、Parse HTML、requestIdleCallback
。当然,这只是简单的JS
代码,对于异步、postMessage()
等情况,执行方式又是不同的!
卡顿与丢帧
对于一般的电脑,屏幕的图像(页面)刷新频率大概是60Hz
,即1s
刷新60
次(约16ms
一次)。实际上,浏览器内核支撑自身体系运行也需要消耗一些时间,所以留给我们的时间差不多只有10ms
。如果一段JS
代码的运行时间过多,导致一帧数据的解析与渲染超过16ms
,就会造成丢帧,现象则是页面滚动卡顿,动画不流畅等等。
网友评论