简述浏览器渲染原理

-

浏览器渲染过程

大致过程如下:

1 浏览器获取 HTML 文件构建成文档对象模型树 DOM(Document Object Model)Tree

DOM 树的构建是一个深度优先遍历的过程,当前节点的子节点全部构建才会构建下一个同级节点。DOM 的根节点为 document 对象。

DOM 树的生成过程会被CSS和JS加载执行阻塞。解析过程的实际结束会触发 DOMContentLoaded 事件

2 当解析到样式定义,不管是样式文件还是嵌入的 CSS 都会被解析成层叠样式表模型 CSSOM(CSS Object Model)

3 根据文档模型 DOM 和样式模型CSSOM生成渲染树 Render tree,渲染树中包含DOM中除了不可见(display: none 或 )的所有元素。渲染树即使DOM的直观展示,包含了 DOM 的相应对象和计算后的样式。

4 渲染树的每个元素包含的内容都是计算过的,它被称之为布局 layout,浏览器使用一种流式处理的方法,只需要一次 pass绘制操作就可以布局所有的元素(tables需要多次pass 绘制,pass 表示像素处理和顶点处理)。

5 最后布局完成,渲染树将转化为屏幕上的实际内容,这一步被称为绘制 painting.

JS脚本和CSS文件对渲染过程的影响

CSS 对页面渲染影响

CSS 可以理解为渲染阻塞资源,当遇到样式定义时,浏览器将不会继续渲染已经处理的内容, JS 的执行也会暂停,直到处理 CSSOM 构建完毕。但 CSS 加载不会阻塞DOM树的解析

 

如果 CSS 文件定义在 head 标签中,则 CSSOM 会先于 DOM 树构建,构建完 CSSOM 后,浏览器就可以边构建 DOM,边完成渲染。

 

反之如果 CSS 文件定义在所有标签之后,浏览器就会先构建 DOM 树,构建完 DOM 树才开始构建 CSSOM 然后会重新渲染页面,导致资源浪费和不好的交互体验。可能会出现样式混乱、白屏等情况。

 

所以为了优化首屏的效果,需要将尽量精简的CSS并放在 head 标签中,不重要的 CSS 也可以选择放在所有标签后面,或者使用媒体查询解除 CSS 对渲染的阻塞。

 

没有 js 的理想情况下,html 与 css 会并行解析,分别生成 DOM 与 CSSOM,然后合并成 Render Tree,进入 Rendering Pipeline;但如果有 js,css 加载会阻塞后面 js 语句的执行,而(同步)js 脚本执行会阻塞其后的 DOM 解析(所以通常会把 css 放在头部, js 放在 body 尾)

 

JS对页面渲染影响

JS 可以理解为解析器阻塞,当解析器遇到 script 标签时,HTML 解析器会被阻塞,DOM 的解析过程会被暂停,直到脚本执行完成。JS 中可以读取和修改 DOM 属性和 CSSOM 属性。

script 标签有两个特殊属性:defer和async
这两个属性可以改变阻塞情况,这两个属性都会异步下载脚本文件,但是执行时机不同。
这里讨论的 script 标签只有在使用 src 属性引入外部脚本的情况下。

  • defer
    开启新的线程下载脚本文件,并且脚本文件会在文档解析完成后执行。执行完 defer 定义的脚本文件后会触发 DOMContentLoaded 事件

如果有多个声明了 defer 的脚本,则会按顺序下载和执行

  • async
    开启新线程下载文件,下载完毕后会立即执行,并阻塞 HTML 解析。

如果有多个声明 async 的脚本,其下载和执行都是异步的,不能确保彼此的先后顺序

async 会在 load 事件之前执行,但并不能确保与 DOMContentLoaded 的执行先后顺序

综上所述,defer 属性和 async 属性都能防止脚本下载阻塞页面渲染。如果脚本之间没有依赖关系,可以使用 async 属性,如果脚本之间有依赖关系,应使用 defer 属性。如果同时使用 async 和 defer 属性,后者不起作用,浏览器行为由 async 属性决定。

 

回流和重绘

重绘 Repaint

当各种盒子的大小位置都已确定,而是改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸没有变,此时浏览器会执行一次重绘。

回流 Reflow

当改变影响文档内容或结构的操作,就会触发回流 Reflow,在页面第一次加载的时候,会触发一次回流。

回流的操作成本要比重绘要高的多,DOM 树上的每个节点都有回流 Reflow 的方法,当一个节点回流时,可能会导致子节点、父节点或者兄弟节点都进行回流操作,可能会造成卡顿或者在移动端产生耗电问题。

完成回流过程后,浏览器会重新绘制受影响的部分到屏幕上,回流一定会触发重绘。

会触发回流的操作:

  • 更改 DOM 的操作,增删改DOM,位置发生变化
  • CSS 属性更改
  • 修改元素的 classname
  • 浏览器窗口变化
  • 修改默认字体
  • display:none,而 visibility:hidden 只会触发 repaint,因为没有发现位置变化

note:有些情况下,比如修改了元素的样式,浏览器并不会立刻 reflow 或 repaint 一次,而是会把这样的操作积攒一批,然后做一次 reflow,这又叫异步 reflow 或增量异步 reflow。

那么如何减小回流带来的性能损耗:

  • 减少对 DOM 的多次修改,或使用修改 classname 实现样式的修改,减少回流次数
  • 如果对 DOM 操作过多可以离线修改 DOM,修改完再添加到页面中
  • 避免使用 table 布局,因为很小的改动都会导致整个 table 重新布局。

浏览器如何解析 CSS选择器

CSS选择器的解析是从右到左解析,先找到右节点,对于每一个节点向上查找父节点,直到找到根元素或符合匹配规则的元素,则结束该分支的遍历。

试想如果从左到右解析的话,发现不符合规则需要回溯造成很大的资源浪费,在性能方面要比从左到右解析要慢很多。

优化渲染性能

  • 优化 JS 执行效率

    1. 使用 requestAnimationFrame 替换 setTimeout和setInterval 实现动画。
    2. 长耗时的不影响渲染的计算放到js worker中执行,避免影响页面解析渲染

降低计算复杂度,降低CSS选择器的复杂度,使用明确简单的CSS选择器能节省计算过程,例如

  .box:nth-last-child(-n+1) .title {}
  // 改善后
  .final-box-title {}
  • 避免大规模复杂布局

布局就是计算DOM元素的大小和位置的过程,如果你的页面中包含很多元素,那么计算这些元素的位置将耗费很长时间。布局的主要消耗在于:需要布局的DOM元素的数量和布局过程的复杂程度

具体如下:

    1.尽量避免触发布局:
当你修改了元素的属性之后,浏览器将会检查为了使这个修改生效是否需要重新计算布局以及更新渲染树,对于DOM元素的几何属性修改,比如width/height/left/top等,都需要重新计算布局。对于不能避免的布局,可以使用Chrome DevTools工具的Timeline查看布局的耗时,以及受影响的DOM元素数量。

          2.使用flex布局比定位和浮动布局性能更优

     3.避免强制同步布局

根据渲染流程,JS脚本是在layout之前执行,但是我们可以强制浏览器在执行JS脚本之前先执行布局过程,这就是所谓的强制同步布局。

 requestAnimationFrame(logBoxHeight);
 // 先写后读,触发强制布局
 function logBoxHeight() {
 // 更新box样式
     box.classList.add('super-big');
 // 为了返回box的offersetHeight值
 // 浏览器必须先应用属性修改,接着执行布局过程
     console.log(box.offsetHeight);
 }
 // 先读后写,避免强制布局
 function logBoxHeight() {
 // 获取box.offsetHeight
     console.log(box.offsetHeight);
 // 更新box样式
     box.classList.add('super-big');
 }

在JS脚本运行的时候,它能获取到的元素样式属性值都是上一帧画面的,都是旧的值。因此,如果你在当前帧获取属性之前又对元素节点有改动,那就会导致浏览器必须先应用属性修改,结果执行布局过程,最后再执行JS逻辑。

          4.简化绘制复杂度、减少绘制区域

Paint就是填充像素的过程,通常这个过程是整个渲染流程中耗时最长的一环,因此也是最需要避免发生的一环。如果Layout被触发,那么接下来元素的Paint一定会被触发。当然纯粹改变元素的非几何属性,也可能会触发Paint,比如背景、文字颜色、阴影效果等。

     5.函数防抖节流

当系统存在一些高频触发的事件函数,例如窗口resize事件、滚动事件和用户输入事件,通常会导致连续触发,增加浏览器负担,导致卡顿影响用户体验。此时可以使用函数的防抖和节流减少调用频率,同时不影响实际效果。

 

-

posted @ 2022-06-19 20:36  古墩古墩  Views(120)  Comments(0Edit  收藏  举报