浏览器渲染原理
浏览器是如何渲染页面的
当浏览器的网络线程收到HTML文档后,会产生一个渲染任务,并将其传递给渲染主线程的消息队列
在事件循环过程中,渲染主线程取出队列中的渲染任务,开启渲染流程
渲染流程
1、解析HTML----Parse HTML
解析过程中,遇到CSS解析CSS,遇到JS执行JS,为了提高效率,开始解析前,浏览器会启动一个预解析线程去下载外部的CSS文件和JS文件
主线程
解析到link时,如果此时的CSS文件还没有下载解析完成,主线程不会等待,继续解析后面的HTML,所以css不会阻塞HTML解析
主线程解析到script,主线程会停下来,直到这个js被下载并将全局代码解析执行完成后,主线程才会继续解析,这是因为js代码可以对DOM进行修改,必须暂停DOM树的生成,这就是为什么js会阻塞HTML解析的原因
这一步完成后,会生成DOM树和CSSOM树,内部样式、内联样式、外部样式、浏览器的默认样式都会包含在CSSOM树中
2、样式计算----style
主线程
遍历DOM树,依此为每个节点计算最终样式(Computed Style)
在这个过程中,会将预设值转为绝对值,red->rgb(255,0,0),相对单位转为绝对单位em->px
这一步完成后,会生成一颗带Computed Style的DOM树
3、布局----layout
主线程
依此遍历DOM的每一个节点,计算每一个节点的几何信息,比如节点的宽高、相对包含块的位置
很多时候,layout树和DOM树不是一一对应的
比如display:none的节点没有几何信息,所以不会生成到layout树,DOM上不存在伪元素节点,但是它们拥有几何信息,所以会生成到layout树
4、分层----layer
主线程
将布局树进行分层
将来某一层发生变动后,只需要对该层进行处理,提高了效率
滚动条、堆叠上下文、transform、opacity 等样式都会或多或少的影响分层结果,也可以通过will-change
属性更大程度的影响分层结果
在控制台可以查看到当前页面的分层信息
5、绘制----paint
主线程
会为每一层生成绘制指令集,描述这一层该如何画出来
完成绘制后,渲染主线程将每层的绘制信息交给合成线程
6、分块----tiling
合成线程
将每一层进行分块,分为多个更小的区域,多个线程完成分块工作
7、光栅化----raster
合成线程在分块后,将分块信息交给GPU进程
进行光栅化
GPU 进程会开启多个线程来完成光栅化,并且优先处理靠近视口区域的块。
光栅化的结果,就是一块一块的位图
8、画----draw
合成线程
拿到每个层、每个块的位图后,生成一个个「指引(quad)」信息。
指引会标识出每个位图应该画到屏幕的哪个位置,以及会考虑到旋转、缩放等变形。
变形发生在合成线程,与渲染主线程无关,这就是transform
效率高的本质原因。
合成线程会把 quad 提交给 GPU 进程,由 GPU 进程产生系统调用,提交给 GPU 硬件,完成最终的屏幕成像
重排(reflow)
reflow的本质是重新计算layout树
如果进行了影响布局树的操作后,就得重新计算布局树,会引发layout,为了避免连续的操作导致布局树反复计算,浏览器会合并操作,当js代码完成后在统一进行计算,所以改动属性造成的reflow是异步完成的,正因如此,在修改后去获取布局属性,拿到的可能不是最新的值,浏览器在反复权衡下,最终决定获取属性立即reflow
重绘(repaint)
repaint的本质是重新计算分层信息生成绘制指令集
当改动了可见样式后,需要重新计算,会引发repaint 由于元素的布局信息也属于可见样式,所以reflow一定会引起repaint
为什么transform效率高
transform不会影响布局也不会影响绘制指令,他影响的只是渲染流程的最后一个draw阶段,draw阶段运行在合成线程中,不会影响到主线程,反之,渲染主线程很忙碌,也不会影响到transform的变化
如何减少重排重绘
使用 CSS 动画代替 JavaScript 动画
:CSS 动画可以使用浏览器的硬件加速,性能更好。相比之下,使用 JavaScript 实现的动画通常需要在每一帧重新计算样式和布局,会引起较多的重排和重绘。避免频繁操作样式
:在 JavaScript 中频繁修改元素的样式会导致多次重排和重绘。为了减少这种情况,可以将需要修改的样式属性合并到一个 CSS 类中,然后通过添加或移除类名的方式来改变样式。使用 transform 属性
:transform 属性可以对元素进行位移、缩放、旋转等变换,而不会引起重排和重绘。通过使用 transform,可以更好地优化动画效果。- 使
用 DocumentFragment
:当需要向页面中添加大量的 DOM 元素时,可以使用 DocumentFragment。DocumentFragment 是一个轻量级的文档片段,可以在其中构建 DOM 结构,然后一次性将其添加到页面中,这样可以减少重排的次数。 避免强制同步布局
:有些属性或方法会导致强制同步布局,比如读取元素的尺寸或位置信息(比如 offsetTop、offsetWidth 等)。在可能的情况下,避免频繁使用这些属性或方法,或者将它们的使用集中在一起,以减少重排的次数。分离读和写操作
:尽量将读操作和写操作分离开来。先进行所有的写操作,然后再进行读操作,这样可以最大程度地减少重排的次数。