Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

浏览器渲染 #18

Open
wqhui opened this issue Dec 10, 2021 · 0 comments
Open

浏览器渲染 #18

wqhui opened this issue Dec 10, 2021 · 0 comments

Comments

@wqhui
Copy link
Owner

wqhui commented Dec 10, 2021

概念介绍

  • 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
关键渲染流程
避免大规模、复杂的布局
带你走近浏览器的渲染流水线
无线性能优化

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant