浏览器渲染过程解析

代码已经关联到github: 链接地址 文章有更新也会优先在这,觉得不错可以顺手点个star,这里会持续分享自己的开发经验(:

概念介绍

  • DOM Tree:浏览器将HTML解析成树形的数据结构。
  • CSSOM(CSS Object Model):页面的所有css样式的对象模型。
  • Render Tree: DOM Tree和CSSOM合并后生成Render Tree。
  • layout(布局render树): 有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系,从而去计算出每个节点在屏幕中的位置。
  • painting(绘制render树): 按照算出来的规则,通过GUI绘制页面像素信息。
  • reflow(回流,重排,同layout):,当浏览器发现某个部分发生了点变化影响了布局,需要倒回去重新渲染,内行称这个回退的过程叫 reflow。reflow 会从 这个 root frame 开始递归往下,依次计算所有的结点几何尺寸和位置。脱离文档流的布局会减少整体网站的回流和重绘。
  • repaint(重绘,同painting):改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸没有变。
  • 强制同步布局:渲染的流程是先执行JavaScript脚本,然后是样式计算,然后是布局。但是,我们还可以强制浏览器在执行JavaScript脚本之前先执行布局过程,这就是所谓的强制同步布局。(后续有详细介绍)

渲染流程

image.png

解析html建立DOM Tree

因为浏览器并不认识html,所以这里将html解析一遍。
字节 → 字符 → 令牌 → 节点 → 对象模型。image.png

解析CSS,生成CSSOM 。

字节 → 字符 → 令牌 → 节点 → 对象模型。

会中断页面渲染

  1. CSS Parser 解析完 CSS 脚本(linkstyle 标签内的css)后,会生成 CSSStyleSheet( document.styleSheets 可获得当前页面所有的)
  2. 将css属性标准化(如将color转化为rgb)

image.png

  1. 通过继承层叠规则计算具体样式,生成CSSOM** **css大纲

浏览器 CSS 匹配核心算法的规则是以 从右向左 方式匹配节点的,这样做是为了减少无效匹配次数,从而匹配快、性能更优。
同时为了加快样式的解析,选择器层级少也是一个优势,但是基本不会是项目的性能瓶颈。
image.png
QA:加快首屏渲染,由于中断渲染存在,所以减少首屏非必要样式。

将CSSOM结合DOM Tree合并成Render Tree。
  1. 删除所有不可见的元素。例如<head><script>,和<meta>,和具有hidden属性的HTML元素。
  2. 通过 CSSOM 找出当前渲染树中的哪些元素与 CSS 选择器匹配。任何匹配的选择器的 CSS 规则都将应用于渲染树的该节点。注意这里 display:none 是特殊的,也会被删除。
布局Render Tree(layout)​

布局从上到下进行的,因为每个元素的定位、宽度和高度都是根据其父节点计算的。
回流也会回到这一步

  1. 从 DOM 树的根节点开始遍历每个可见节点。
    • 某些节点不可见(例如脚本标记、元标记等),因为它们不会体现在渲染输出中,所以会被忽略。
    • 某些节点通过 CSS 隐藏,因此在渲染树中也会被忽略,例如,上例中的 span 节点—不会出现在渲染树中,—因为有一个显式规则在该节点上设置了“display: none”属性。
  2. 对于每个可见节点,为其找到适配的 CSSOM 规则并应用它们。
  3. 设置可见节点,连同其内容和计算的样式。

分层绘制Render Tree(paint)

重绘也会重新从这里重新去渲染。

布局完后,浏览器会把Render Tree分层(composite,详情可看图层介绍)。
浏览器会把一个图层拆分为一个个小的绘制指令,然后将指令按照顺序排成一个列表。(绘制过程和使用canvas进行绘制图类似。)
接着是绘制,浏览器会将各层划分为图块,这么做的目的是因为视口显示的内容有限,如果直接将整个结构进行绘制开销比较大,所以浏览器会优先将视口内的图块转为位图,这个过程叫栅格化。
最后浏览器会GPU会将各层合成,显示在屏幕上。

回流和重绘

回流

当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来。这个过程就是回流(也叫重排)。

引发元素位置变化和获取元素实时属性的操作会引起回流:

  • DOM 几何属性改变:width、height、padding、margin、left、top、border
  • DOM节点发生改变:比如删除、插入和显示(display控制)节点
  • 获取DOM节点的实时属性:比如getComputedStyle、getBoundingClientRect和offsetTop等
重绘

当我们对 DOM 的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式(跳过了上图所示的回流环节)。这个过程叫做重绘。

仅在当前位置发生变化的操作会引起重绘:

  • DOM外观属性改变:visibility、color、background-color

QA:如何减少回流和重绘?
避免使用触发重绘和回流的CSS属性,将频繁重绘回流的元素创建为一个独立图层。

  • 使用transform实现效果:可以避开回流和重绘,直接进入合成阶段(图块-栅格化-合成-显示)
  • 用opacity替代visibility:visibility会触发重绘
  • 使用class替代DOM频繁操作样式
  • DOM离线后修改,如果有频繁修改,可以先把DOM隐藏,修改完成后再显示
  • 不要在循环中读取DOM的几何属性值:如offsetHeight
  • 尽量不要使用table布局,小改动会造成整个table重新布局

图层介绍

渲染步骤中就提到了composite概念,浏览器渲染中的图层又两种:

  • 渲染图层,又称默认复合层,是页面普通的文档流。我们虽然可以通过绝对定位,相对定位,浮动定位脱离文档流,但它仍然属于默认复合层,共用同一个绘图上下文对象(GraphicsContext)。
  • 复合图层,又称图形层。它会单独分配系统资源,每个复合图层都有一个独立的GraphicsContext。(当然也会脱离普通文档流,这样一来,不管这个复合图层中怎么变化,也不会影响默认复合层里的回流重绘)

Chrome源码调试 -> More Tools -> Rendering -> Layer borders中看到,黄色的就是复合图层信息

复合图层的作用?(为什么硬件加速会使页面流畅)
  • 合成层的位图,会交由 GPU 合成,比 CPU 处理要快
  • 当需要 repaint 时,只需要 repaint 本身,不会影响到其他的层
  • 对于 transformopacity 效果(没有发生形变和rgb变化),不会触发 layout 、layer和 paint,直接进入合成线程处理
如何变成复合图层(硬件加速)
  • 本身原因
    • transform的3D变化,如translate3dtranslateZ (最常用)
    • transform非3D变化 和 opacity,动画执行的过程中才会创建合成层,动画没有开始或结束后元素还会回到之前的状态
    • will-chang 属性,一般值为opacity与translate使用
    • 3D元素(如video) 或者 硬件加速的 2D Canvas 元素
    • 隐藏规则1:如果a是一个复合图层,而且b在a上面,那么b也会被隐式转为一个复合图层
    • 隐藏规则2:fixed布局在高分辨率下也会单独一层
  • 和其他复合图层有重叠
  • 后代是复合图层

同时自身有transform、 overflow不为visible、fixed
QA:什么是复合图层,如何变成复合图层

复合图层需谨慎

由于复合图层可以提高性能,但是用户机器不一,有些机器、浏览器资源消耗过度,页面反而会变的更卡。
且使用复合图层时,尽量提升该元素的z-index层级,或者提升父元素的,使其脱离文档流,因为在硬件加速元素的后面其它元素(层级比这个元素高的,或者相同的,并且releative或absolute属性相同的),会默认变为复合层渲染。这就涉及到可能是图层的隐藏规则:如果a是一个复合图层,而且b在a上面,那么b也会被隐式转为一个复合图层

强制布局同步和快速连续布局

强制布局同步

image.png
当我们修改了元素的几何属性,导致浏览器触发重排或重绘时。它会把该操作放进渲染队列,等到队列中的操作到了一定的数量或者到了一定的时间间隔时,浏览器就会批量执行这些操作。
强制同步布局,是指 JavaScript 强制将计算样式和布局操作提前到当前的任务中 , 也就是浏览器会立即执行渲染队列的任务。
常见的如先写后读,增加删除元素后读

//先写后读
function logBoxHeight() {
  //正确做法是倒过来...
  box.classList.add('claaaas');
  console.log(box.offsetHeight);
}
//增加删除元素,获取父元素几何信息
let main_div = document.getElementById("mian_div")      
let textnode = document.createTextNode("blue")
main_div.appendChild(new_node);
console.log(main_div.offsetHeight)

快速连续布局

在修改布局时,同时去读。如下,将子元素的宽度设为父的宽度,每次设置完毕下次又会去获取,导致不断的重复布局。

const paragraphs = document.getElementById("mian_div").children

function resizeAllParagraphsToMatchBlockWidth() {
	for (var i = 0; i < paragraphs.length; i++) {
  	paragraphs[i].style.width = box.offsetWidth + 'px'; //w + 'px'
  }
}

const w = box.offsetWidth
function resizeAllParagraphsToMatchBlockWidthCorrect() {
	for (var i = 0; i < paragraphs.length; i++) {
  	paragraphs[i].style.width = w + 'px'
  }
}

QA:如何减少强制布局同步、快速连续布局?
避免先写后读,尽量先读后写,更不要不要在循环中读取

参考

探究 CSS 解析原理
CSS 的工作原理:在关键渲染路径中解析和绘制 CSS
关键渲染流程
避免大规模、复杂的布局
带你走近浏览器的渲染流水线
无线性能优化

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值