浏览器渲染流程
#
浏览器渲染流程详细版#
1.1 DOM树构建渲染器进程接受到的数据也就是HTML。渲染器进程的核心任务就是把html、CSS、 js、 image等 资源渲染成用户可以交互的web页面。渲染器进程的主线程将html进行解析,构造DOM数据结构。DOM也就是文档对象模型,是浏览器对页面在其内部的表示形式,是web开发程序员可以通过JS与之交互的数据结构和API。html首 先通过tokeniser标记化,通过词法分析将输入的ht ml内容解析成多个标记,根据识别后的标记进行DOM树构造,在DOM树构建过程中会创建document对象,然后以document的为根节点的DOM树,不断进行修改,向其中添加各种元素。
#
1.2 渲染阻塞htmI代码中往往会引入一些额外的资源,比如图片、CSS、JS脚本等,图片和CSS这些资源需要通过网络下载或从缓存中直接加载,这些资源不会阻塞html的解析,因为他们不会影响DOM树的生成,但当HTML 解析过程中遇到script标签,就会停止html解析流程,转而去加载解析并且执行JS。这是因为浏览器并不知道JS执行是否会改变当前页面的HTML结构,如果JS代码里用了document.write方法来修改html,之前的和html解析就没有任何意义了,这也就是为什么我们一直说要把script标签要放在合适的位置,或者使用async或defer属性来异步加载执行JS。
#
1.3 Layout Tree在html解析完成后,我们就会获得一个DOM Tree (树),但我们还不知道DOM Tree. 上的每个节点应该长什么样子,主线程需要解析CSS,并确定每个DOM节点的计算样式,即使你没有提供自定义的CSS样式,浏览器会有自己默认的样式表,比如h2的字体要比h3的大。在知道DOM结构和每个节点的样式后,我们接下来需要知道每个节点需要放在页面上的哪个位置,也就是节点的坐标以及该节点需要占用多大的区域,这个阶段被称为layout布局,主线程通过遍历dom和计算好的样式来生成L ayout Tree。Layout Tree 上的每个节点都记录了x、y坐标和边框尺寸。这需要注意的是DOM Tree和L ayout Tree并不是一一对应的,设置了display:none的节点不会出现在L ayout Tree 上,而在before伪类 中添加J content值的元素content中的内容会出现在L ayout Tree 上,不会出现在DOM树里,这是因为DOM是通过HTML解析获得的,并不关心样式,而L ayout Tree是根据DOM和计算好的样式来生成,Layout Tree是和最后展示在屏幕上节点是对应的。
#
1.4 绘制(paint)现在我们已经知道了元素的大小形状和位置,但还不知道以什么样的顺序绘制(paint) 这个节点,例如z index这个属性会影响节点绘制的层级关系,如果按照dom的层 级结构来绘制页面则会导致错误的渲染。所以为了保证在屏幕上展示正确的层级,主线程遍历L ayout Tree创建一个绘制记录表(Paint Record) ,该表记录了绘制的顺序,这个阶段配称为绘制(Paint)。
#
1.5 栅格化现在知道了文档的绘制顺序,终于到了该把这些信息转化成像素点显示在屏幕上了,这个行为被称为栅格化(Rasterng) 。chrome最早使用了一种很简单的方式,只栅格化用户可视区域的内容,当用户滚动页面时,再栅格化更多的内容来填充缺失的部分,这种方式带来的问题就是会导致展示延迟。现在chrome进行了优化升级,使用了种更为复杂的栅格化流程叫做合成(compositing),合成是一种将页 面各个部分分成多个图层,分别对其进行栅格化,并在合成器线程(compositor Thread) 中单独进行合成页面,简单来说就是页面所有的元素按照某种规则进行分图层,并把图层都栅格化好了,然后只需要把可视区的内容组合成一帧展示给用户即可。
#
1.6 layer tree主线程遍历L ayout Tree生成layer tree, 当L ayer Tree生 成完毕和绘制顺序确定后,主线程将这些信息传递给合成器线程,合成器线程将每个图层栅格化,由于一层可能想页面的整个长度一样大, 因此合成器线程将他们切分为许多图块(tiles) ,然后将每个图块发送给栅格化线程(Raster Thread) ,栅格化线程栅格化每个图块,并将他们存储在GPU内存中,当图块栅格化完成后,合成器线程将收集成为draw quads 的图块信息,这些信息里记录了图块字啊内存中的位置和在页面的那个位置绘制图块的信息,根据这些信息合成器线程生成一个合成器帧(Compositor Frame) 然后合成器Frame (帧)通过IPC传递给浏览器进程,接着浏览器进程将合成器帧传送到GPU,然后GPL J渲染展示到屏幕上。
当页面发生变化时,比如滚动了当前的页面,都会生成一个新的合成器帧,新的帧再传给GPU,然后再次渲染到屏幕上。
#
浏览器渲染流程简要版html解析生成dom树
遇到css时, css解析器将计算并生成cssOM
将dom树和cssOM树合成渲染树, 此时计算元素布局等信息
将渲染树生 成合成树
渲染主线程生成绘制指令列表提交给合成线程
合成线程利用栅格化生成位图,此时会用GPU进程来进行加速
提交给浏览 器主进程进行界面展示
#
什么是重排、重绘,怎样避免?#
重绘重排当我们改变了一个元素的尺寸位置属性时,会重新进行样式计算(computed style) 布局(layout) 绘制(paint) 以及后面的所有流程,这种行为成为重排。
当改变了某个元素的颜色属性时不会重新触发布局,但还是会触发样式计算和绘制这就是重绘。
我们可以发现重排和重绘都会占用主线程,还有JS也会运行在主线程,所以就会出现抢占执行时间的问题,如果你写了一个不断导致重排重绘的动画,浏览器则需要在每一帧都运行 样式计算布局和绘制的操作。
#
优化方式我们知道当前页面以每秒60帧的刷新率时才不会让用户感觉到页面卡顿,如果在运行动画是还有大量的JS任务需要执行,因为布局、绘制和js执行都是在主线程运行的,当在帧的时间内布局和绘制结束后, 还有剩余时间js就会拿 到主线程的使用权,如果js执行时间过长,就会导致在下一帧开始时js没有及时归还主线程,导致下一帧动画没有按时渲染,就会出现页面的卡顿。
- 第1种优化方式:
requestAnimationFrame,它会在每帧被调用,通过回调API的回调, 可以把js运行任务分成一些更小的任务块, 在每一帧事件用完前暂停js执行归还主线程,这样的话在下一帧开始时,主线程就可以按时执行布局和绘制。
- 第2种优化方式:
栅格化的整个流程不占用主线程,只在合成器线程和栅格线程中运行,这就意味着它无需和js抢占主线程。如果反复进行重绘和重排可能会导致掉帧,这是因为有可能js执行阻塞了主线程,而CSS中有个动画属性transform,通过该属性实现的动画不会经过布局和绘制,而是直接运行在合成器线程和栅格线程,所以不会受到主线程中js执行的影响。更重要的是听过transform实现的动画由于不需要经过布局绘制样式计算等操作,所以节省了很多运算事件(方便实现负责的动画)
#
避免重绘重排具体方案- CSS
- 使用transform 替代top等位移;
- 使用visibility 替换display: none
- 避免使用table布局
- 尽可能在DOM树的最末端改变class
- 避免设置多层内联样式,尽量层级扁平
- 将动画效果应用到position属性为absolute或fixed的元素上
- 避免使用CSS表达式
- 将频繁重绘或者回流的节点设置为图层,比如video, iframe
- CSS3硬件加速(GPU加速) ,可以是transform: translateZ(0)、 opacity、 filters、 will-change ,Will-change提前告诉浏览器元素会发生什么变化;
- JS
- 避免频繁操作样式,合并操作
- 避免频繁操作DOM,合并操作;
- 防抖节流控制频率;
- 避免频繁读取会引发回流/重绘的属性,比如上面的C、O、S属性
- 对具有复杂动画的元素使用绝对定位