温故知新(八四)说说浏览器渲染流程,分层之后再什么时候合成,什么是重排、重绘,怎样避免

说说浏览器渲染流程,分层之后再什么时候合成,什么是重排、重绘,怎样避免

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 



一、浏览器渲染流程详细版

再回顾一下浏览器工作步骤:

  1. 处理HTML标签构建DOM树。
  2. 处理CSS标签构建CSSOM树。
  3. DOM和CSSOM合并后生成Render Tree。(Render tree渲染树只包含需要显示在页面的节点)。
  4. 有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系,从而去计算出每个节点在屏幕中的位置。Layout(reflow)计算每个对象的确切位置和大小。
  5. 最后是绘制(Paint),把最终的渲染树对象渲染成屏幕像素。

1.1 DOM 树构建

渲染进程接收到的数据也就是 HMTL 。渲染器进程的核心任务就是把 html、css、js、image 等资源渲染成用户可以交互的 web 页面。渲染器进程的主线程将 html 进行解析,构造 DOM 数据结构。DOM 也就是文档对象模型,是浏览器对页面在其内部的表示形式,是 web 开发程序员可以通过 JS 与之交互的数据结构和 API。html 首先通过 tokenier 标记化,通过词法分析将输入的 html 内容解析成多个标记,根据识别后的标记进行 DOM 树构造,在 DOM 树构建过程中会创建 document 对象,然后以 document 的为根节点的 DOM 树,不断进行修改,向其中添加各种元素。

1.2渲染阻塞

html 代码中往往会引入一些额外的资源,比如图片、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 和计算好的样式来生成 Layout Tree。Layout Tree 上的每个节点都记录了 x,y 坐标和边框尺寸。这需要注意的是 DOM Tree 和 Layout Tree 并不是一 一 对应的,设置了 display:none 的节点不会出现在 Layout Tree 上,而在 before 伪类中添加了 content 值得元素 content 中的内容会出现在 Layout Tree 上,不会出现在 DOM 树里,这是因为 DOM 是通过 HTML 解析获得的,并不关系样式,而 Layout Tree 是根据 DOM 和 计算好的样式来生成,Layout Tree 是和最后展示在屏幕上节点是对应的。

1.4 绘制

现在我们已经知道了元素的大小形状和位置,但还不知道以什么样的顺序绘制(paint)这个节点,例如 z-index 这个属性会影响节点绘制的层级关系,如果按照 dom 的层级结构来绘制页面则会导致错误的渲染。所以为了保证在屏幕上展示正确的层级,主线程遍历 Layout Tree 构建一个绘制记录表(Paint Record),该表记录了绘制的顺序,这个阶段被称为绘制(Paint)。

1.5 栅格化

现在知道了文档的绘制顺序,终于到了该把这些信息转化成像素点显示在屏幕上了,这个行为被称为栅格化(Rastering)。chrome 最早使用了一个很简单的方式,只栅格化用户可视区域的内容,当用户滚动页面是,再栅格化更多的内容来填充缺失的部分,这种方式带来的问题就是会导致展示演示。现在 chrome 进行了优化升级,使用了一种更为复杂的栅格化流程叫做合成(compositing),合成是一种将页面各个部分分成多个图层,分别对其进行栅格化,并在合成器线程(compositor thread)中单独进行合成页面,简单来说就是页面所有元素按照某种规则进行分图层,并把图层都栅格化好了,然后只需要把可视区的内容组合成一帧展示给用户即可。

1.6 layer tree

主线程遍历 Layout Tree 生成 layer tree ,当 Layer Tree 生成完毕和绘制顺序确定后,主线程将这些信息传递给合成器线程,合成器线程将每个图层栅格化,由于一层可能想页面的整个长度一样大,因此合成器线程将他们切分为许多图块(tiles),然后将每个图块发送个栅格化线程,栅格化线程栅格化每个图块,并将它们存储在 GPU 内存中,当图块栅格化完成后,合成器线程将收集称为 draw quads 的图块信息,这些信息里面记录了图块字啊,内存中的位置和在页面的哪个位置绘制图块的信息,根据这些信息合成器线程生成一个合成器帧,然后合成器 Frame(帧)通过 IPC 传递给浏览器进程,接着浏览器进程将合成器帧传送到 GPU,然后 GPU 渲染展示到屏幕上。
当页面发生变化时,比如滚动了当前的页面,都会生成一个新的合成器帧,新的帧再传给 GPU,然后再次渲染到屏幕上。

二、浏览器渲染流程简要版

1.html 解析生成 DOM 树
2.遇到 css 时,css 解析器将计算并生成 CSSOM
3.将 dom 树和 CSSOM 树合成生成渲染树,此时计算元素布局等信息
4.将渲染书生成合成树
5.渲染主线程生成绘制指令列表提交给合成线程
6.合成线程利用栅格化生成位图,此时会用 GPU 进程来进行加速
7.提交给浏览器主进程进行界面展示

Tips:

display:none 的节点不会被加入Render Tree,而visibility: hidden 则会,所以,如果某个节点最开始是不显示的,设为display:none是更优的。

display:none 会触发 reflow,而 visibility:hidden 只会触发 repaint,因为没有发现位置变化。

有些情况下,比如修改了元素的样式,浏览器并不会立刻reflow 或 repaint 一次,而是会把这样的操作积攒一批,然后做一次 reflow,这又叫异步 reflow 或增量异步 reflow。但是在有些情况下,比如resize 窗口,改变了页面默认的字体等。对于这些操作,浏览器会马上进行 reflow。

三、什么是重排、重绘,怎样避免

3.1 重绘重排

当我们改变了一个元素的尺寸位置属性时,会重新进行样式计算(computed style)布局(layout)绘制(paint)以及后面的所有流程,这种行为称为重排。

重排(回流):当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来。
重绘:当我们对 DOM 的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式,当改变了某个元素的颜色属性时不会重新触发布局,但还是会触发样式计算和绘制这就是重绘。

回流必定会发生重绘,重绘不一定会引发回流。重绘和回流会在我们设置节点样式时频繁出现,同时也会很大程度上影响性能。回流所需的成本比重绘高的多,改变父节点里的子节点很可能会导致父节点的一系列回流。

常见引起回流属性和方法:

任何会改变元素几何信息(元素的位置和尺寸大小)的操作,都会触发回流,

  • 添加或者删除可见的DOM元素;

  • 元素尺寸改变——边距、填充、边框、宽度和高度

  • 内容变化,比如用户在input框中输入文字

  • 浏览器窗口尺寸改变——resize事件发生时

  • 计算 offsetWidth 和 offsetHeight 属性

  • 设置 style 属性的值

常见引起重绘属性和方法

我们可以发现重排和重绘都会占用主线程,还有 JS 也会运行在主线程,所以就会出现抢占执行时间的问题,如果你写了一个不断导致重排重绘的动画,浏览器则需要在每一帧都运行样式计算布局和绘制的操作。

3.2 优化方式

我们知道当前页面以每秒 60 帧的刷新率刷新时才不会让用户感到页面卡顿,如果运行动画时还有大量的 JS 任务需要执行,因为布局、绘制和 JS 执行都是在主线程运行的,当在一帧的时间内布局和绘制结束后,还有剩余时间 JS 就会拿到主线程的使用权,如果 js 执行时间过长,就会导致在下一帧开始时 js 没有及时归还主线程,导致下一帧动画没有按时渲染,就会出现页面的卡顿。

3.2.1 第一种优化方式:

requestAnimationFrame ,它会在每一帧被调用,通过回调 API 的回调,可以把 js 运行任务分成更小的任务块,在每一帧时间用完钱暂停 js 执行归还主线程,这样的话在下一帧开始时,主线程就可以按时执行布局和绘制。

3.2.2 第二种优化方式:

栅格化的整个流程不占用主线程,只在合成器线程和栅格线程中运行,这就意味着它无需和 js 抢占主线程。如果反复进行重绘和重排可能导致掉帧,这是因为有可能 js 执行阻塞了主线程,而 css 中有个动画属性 transform,通过该属性实现的动画不会经过布局和绘制,而是直接运行在合成器线程和栅格线程,所以不会受到主线程中 js 执行的影响。更重要的是经过 transform 实现的动画由于不需要经过布局绘制样式计算等操作,所以节省了很多运算时间(方便实现负责的动画)

3.3 避免重绘重排具体方案

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属性
  • 对具有复杂动画的元素使用绝对定位

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值