浏览器渲染HTML页面流程
背景
JavaScript是一种动态类型语言,在编译时并不能准确知道变量的类型,只可以在运行时确定。所以最近在研究V8引擎中JavaScript的编译和执行过程。
在V8中,JavaScript相关代码并非一下完成编译的,而是在某些代码需要执行时,才会进行编译,这就提高了响应时间,减少了时间开销。既然考虑到提高效率,感觉有必要再把页面渲染流程回顾一下。
渲染过程
- 解析HTML源码,构建
DOM Tree
:
在DOM 树中,每个HTML标签都有对应的节点,DOM树的根节点对应HTML标签。 - 解析CSS代码,计算样式,构建
CSSOM Tree
:
解析CSS的时候会按照如下顺序来定义优先级:浏览器默认设置 < 用户设置 < 外链样式 < 内联样式 < html中的style。 - 合并
DOM Tree
和CSSOM Tree
,构建Render tree(渲染树)
:
渲染树会忽略掉不需要渲染的元素,比如head
、display:none
的元素等。而且一大段文本中的每一个行在渲染树中都是独立的一个节点。渲染树中的每一个节点都存储有对应的css属性。 - 浏览器根据
Render tree(渲染树)
把页面绘制到屏幕上显示。
阻塞渲染
由于我们的页面包含JS脚本和CSS,浏览器在渲染页面时,遇到link
和script
,HTML Paser会被阻塞。
默认情况下,CSS 被视为阻塞渲染的资源,这意味着浏览器将不会渲染任何已处理的内容,直至 CSSOM 构建完毕。JavaScript 不仅可以读取和修改 DOM 属性,还可以读取和修改 CSSOM 属性。所以得关注一下几点:
- 解析到CSS 资源时,浏览器会延迟 JavaScript 的执行和 DOM 构建。
- 解析到script 标记时,DOM 构建将暂停,直至脚本完成执行。
- CSSOM 构建时,JavaScript 执行将暂停,直至 CSSOM 就绪。
因此,实际开发过程中,约定的规则:
- CSS 优先:引入顺序上,CSS 资源先于 JavaScript 资源。
- JavaScript 应尽量少影响 DOM 的构建。
备注:
解析器一旦读取到script标签(内联或外联)时,会阻塞DOM构建,它会去请求脚本并执行完成后才继续执行下去。async
和defer
标签除外,async
和defer
属性会让脚本异步执行,不会阻塞浏览器解析。
repaints and reflows
CSS
和JavaScript
往往会修改DOM或者CSSOM,这就会引起浏览器的重排/重绘过程。
重排就是渲染树的一部分必须要更新 并且节点的尺寸发生了变化。这就会触发重排操作。
重绘部分节点需要更新,但是没有改变他的集合形状,比如改变了背景颜色,这就会触发重绘。
记住,重排一定会引起重绘,而重绘不一定会引起重排。
一、 reflow(重排 / 回流)
:根据Render tree进行节点信息计算(Layout)。
当Render Tree刚构建完时,并不包含元素节点的位置和大小信息。计算这些值的过程称为重排或回流。
常见的引起重排的操作:
-
元素尺寸或位置发生改变。
-
元素内容变化(文字数量或图片大小等等)。
-
元素字体大小变化。
-
页面首次渲染。
-
浏览器窗口大小发生改变。
-
增加或删除DOM节点设置 display: none;(重排并重绘) 或visibility: hidden(只有重排)
-
激活CSS伪类(例如::hover)。
-
设置style属性
-
查询某些属性或调用某些方法
-
repaint(重绘)
:根据计算好的信息绘制整个页面(Painting)。
重绘,就是当页面中元素样式的改变并不影响它在文档流中的位置时,例如更改了字体颜色,浏览器会将新样式赋予给元素并重新绘制的过程称。
常见的引起重排的操作: -
设置
color
、visibility
、text-decoration
、box-shadow
。 -
设置
background
相关的CSS,如background
、background-image
、background-position
、background-repeat
、background-size
。 -
设置
border
相关的CSS,如border
、boerder-style
、boerder-radious
。 -
设置
outline
相关的CSS,如outline
、outline-color
、outline-style
、outline-width
。
优化性能
重排一定会引起重绘,而重绘不一定会引起重排,所以要想提升性能,要从减少重排开始入手。
- 避免单独设置
style属性
,可以通过一个class属性
来统一设置。 - 要是对于某节点有大量的操作,建议使用documentFragment,clone该节点,进行修改,然后直接替换以前的节点。
- 要是样式需要通过计算获取,最好把计算结果缓存起来,而不是每次都是从DOM上读取。
- 绝对定位元素的动画不会影响其他大部分元素。实现元素的动画,它的position属性,最好是设为absoulte或fixed,这样不会影响其他元素的布局,还可以让动画处于更高的图层(即:z-index的值更大)这也是从图层的角度进行优化的。
- 动画实现的速度的选择。考虑到资源的消耗,可以适当的放慢,比如动画是以1个像素为单位移动,但是reflow就会过于频繁,如果以3个像素为单位移动则会好很多。
- 少用table布局。因为table中某个元素旦触发了reflow,那么整个table的元素都会触发reflow。必须要使用的可以设置
table-layout:auto
或者table-layout:fixed
这样可以让table一行一行的渲染。 - 用 document.createElement 创建的 script 默认是异步的。所以,通过动态添加 script 标签引入 JavaScript 文件默认是不会阻塞页面的。如果想同步执行,需要将 async 属性人为设置为 false。