# 了解一下渲染原理
浏览器的渲染原理大致都差不多,下面是 Chrome 浏览器的渲染流程

# 关键渲染路径
关键渲染路径指的是浏览器将
HTML,CSS,JS转化为实际运作的网站的必须采取的一系列步骤
- 渲染进程的主线程处理 HTML 并构建 DOM Tree(文档对象模型树)
- 接下来处理
CSS并构建CSSOM Tree(CSS 对象模型树) - 浏览器的布局系统根据
DOM Tree和CSSOM Tree生成 (布局树)Layout Tree(以前叫 Render Object Tree) - 构建(图层树) Layer Tree 以便用正确的顺序展示页面,这棵树的生成与
Layout Tree的构建同步进行 - 生成绘制列表,渲染进程中的主线程 给 合成线程发送 commit 消息,把绘制列表交给合成线程
- 合成线程会根据绘制列表绘制图层
较为专业的术语
- 构建 DOM 树(DOM Tree)
- 样式计算(CSSOM Tree)
- 布局阶段(Layout Tree)
- 分层(Layer Tree)
- 绘制(Paint)
- 分块(Tile)
- 光栅化
- 合成
想象一下,从 0,1 字节流到最后页面展现在你面前,这里面渲染机制肯定很复杂,所以渲染模块把执行过程中化为很多的子阶段,渲染引擎从网络进程拿到字节流数据后,经过这些子阶段的处理,最后输出像素,这个过程可以称为渲染流水线 ,我们从一张图上来看

# DOM Tree
DOM(Document Object Model——文档对象模型)是用来呈现以及与任意 HTML 或 XML 交互的 API 文档。DOM 是载入到浏览器中的文档模型,它用节点树的形式来表现文档,每个节点代表文档的构成部分。
需要说明的是 DOM 只是构建了文档标记的属性和关系,并没有说明元素需要呈现的样式,这需要 CSSOM 来处理。
# 构建流程
获取到 HTML 字节数据后,会通过以下流程构建 DOM Tree:
字节 → 字符 → 令牌 → 节点 → 对象模型(DOM)
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
<title>Critical Path</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg"></div>
</body>
</html>
2
3
4
5
6
7
8
9
10
11

- 编码:浏览器从磁盘或网络读取 HTML 的原始字节,并根据文件的指定编码(例如 UTF-8)将它们转换成各个字符。
- 词法分析(标记化):浏览器对输入字符串进行逐字扫描,根据 构词规则 识别单词和符号,分割成一个个我们可以理解的词汇(学名叫 Token )的过程。
- 语法分析(解析器):对 Tokens 应用 HTML 的语法规则,进行配对标记、确立节点关系和绑定属性等操作,从而构建 DOM Tree 的过程。
词法分析和语法分析在每次处理 HTML 字符串时都会执行这个过程,比如使用 document.write 方法。
# 词法分析(标记化)
HTML 结构不算太复杂,大部分情况下识别的标记会有开始标记、内容标记和结束标记,对应一个 HTML 元素。除此之外还有 DOCTYPE、Comment、EndOfFile 等标记。
标记化是通过状态机来实现的,状态机模型在 W3C 中已经定义好了。
想要得到一个标记,必须要经历一些状态,才能完成解析。我们通过一个简单的例子来了解一下流程。
<a href="www.w3c.org">W3C</a>

- 开始标记:
- Data state:碰到 <,进入 Tag open state
- Tag open state:碰到 a,进入 Tag name state 状态
- Tag name state:碰到 空格,进入 Before attribute name state
- Before attribute name state:碰到 h,进入 Attribute name state
- Attribute name state:碰到 =,进入 Before attribute value state
- Before attribute value state:碰到 ",进入 Attribute value (double-quoted) state
- Attribute value (double-quoted) state:碰到 w,保持当前状态
- Attribute value (double-quoted) state:碰到 ",进入 After attribute value (quoted) state
- After attribute value (quoted) state:碰到 >,进入 Data state,完成解析
- 内容标记:W3C
- Data state:碰到 W,保持当前状态,提取内容
- Data state:碰到 <,进入 Tag open state,完成解析
- 结束标记:
- Tag open state:碰到 /,进入 End tag open state
- End tag open state:碰到 a,进入 Tag name state
- Tag name state:碰到 >,进入 Data state,完成解析
通过上面这个例子,可以发现属性是开始标记的一部分。
# 语法分析(解析器)
在创建解析器后,会关联一个 Document 对象作为根节点。
我会简单介绍一下流程,具体的实现过程可以在 Tree construction 查看。
解析器在运行过程中,会对 Tokens 进行迭代;并根据当前 Token 的类型转换到对应的模式,再在当前模式下处理 Token;此时,如果 Token 是一个开始标记,就会创建对应的元素,添加到 DOM Tree 中,并压入还未遇到结束标记的开始标记栈中;此栈的主要目的是实现浏览器的容错机制,纠正嵌套错误,具体的策略在 W3C 中定义。更多标记的处理可以在 状态机算法中查看。
# CSSOM Tree
# 样式计算
这个子阶段主要有三个步骤
- 格式化样式表
- 标准化样式表
- 计算每个 DOM 节点具体样式
# 格式化样式表
我们拿到的也就是 0,1 字节流数据,浏览器无法直接去识别的,所以渲染引擎收到 CSS 文本数据后,会执行一个操作,转换为浏览器可以理解的结构-styleSheets
如果你很想了解这个格式化的过程,可以好好去研究下,不同的浏览器可能在 CSS 格式化过程中会有所不同,在这里就不展开篇幅了。
通过浏览器的控制台document.styleSheets可以来查看这个最终结果。通过 JavaScript 可以完成查询和修改功能,或者说这个阶段为后面的样式操作提供基石。

# 标准化样式表
什么是标准化样式表呢?先看一段 CSS 文本 👇
body {
font-size: 2em;
}
p {
color: blue;
}
span {
display: none;
}
div {
font-weight: bold;
}
div p {
color: green;
}
div {
color: red;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
有些时候,我们写 CSS 样式的时候,会写font-size:2em;color:red;font-weight:bold,像这些数值并不容易被渲染引擎所理解,因此需要在计算样式之前将它们标准化,如em->px,red->rgba(255,0,0,0),bold->700等等。
上面的代码标准后属性值是什么样子呢

# 计算每个 DOM 节点具体样式
通过格式化和标准化,接下来就是计算每个节点具体样式信息了。
计算规则:继承和层叠
继承:每个子节点会默认去继承父节点的样式,如果父节点中找不到,就会采用浏览器默认的样式,也叫UserAgent样式。
层叠:样式层叠,是 CSS 一个基本特征,它定义如何合并来自多个源的属性值的算法。某种意义上,它处于核心地位,具体的层叠规则属于深入 CSS 语言的范畴,这里就补展开篇幅说了。
不过值得注意的是,在计算完样式之后,所有的样式值会被挂在到window.getComputedStyle当中,也就是可以通过 JS 来获取计算后的样式,非常方便。
这个阶段,完成了 DOM 节点中每个元素的具体样式,计算过程中要遵循 CSS 的继承和层叠两条规则,最终输出的内容是每个节点 DOM 的样式,被保存在 ComputedStyle 中。
想了解每个 DOM 元素最终的计算样式,可以打开 Chrome 的“开发者工具”,选择第一个“element”标签,选择 div 标签,然后再选择“Computed”子标签
# (题外话)另一种说法 CSSOM

跟处理 HTML 一样,我们需要更具 CSS 两个规则:继承和层叠转换成某种浏览器能理解和处理的东西,处理过程类似处理 HTML,如上图 ☝
CSS 字节转换成字符,接着转换成令牌和节点,最后链接到一个称为“CSS 对象模型”(CSSOM) 的树结构内

很多人肯定看这个很熟悉,确实,很多博客都是基于 CSSOM 说法来讲的,我要说的是:
和 DOM 不一样,源码中并没有 CSSOM 这个词,所以很多文章说的 CSSOM 应该就是 styleSheets,当然了这个 styleSheets 我们可以打印出来的
很多文章说法是渲染树也是 16 年前的说法,现在代码重构了,我们可以把 LayoutTree 看成是渲染树,不过它们之间还是有些区别的。
# 生成布局树
上述过程已经完成 DOM 树(DOM 树)构建,以及样式计算(DOM 样式),接下来就是要通过浏览器的布局系统确定元素位置,也就是生成一颗布局树(Layout Tree),之前说法叫 渲染树。
# 创建布局树
在 DOM 树上不可见的元素,head 元素,meta 元素等,以及使用 display:none 属性的元素,最后都不会出现在布局树上,所以浏览器布局系统需要额外去构建一棵只包含可见元素布局树。
我们直接结合图来看看这个布局树构建过程:

为了构建布局树,浏览器布局系统大体上完成了下面这些工作:
- 遍历 DOM 树可见节点,并把这些节点加到布局树中
- 对于不可见的节点,head,meta 标签等都会被忽略。对于 body.p.span 这个元素,它的属性包含 display:none,所以这个元素没有被包含进布局树。
# 布局计算
接下来就是要计算布局树节点的坐标位置,布局的计算过程非常复杂,张开介绍的话,会显得文章过于臃肿,大多数情况下,我们只需要知道它所做的工作是什么,想知道它是如何做的话,可以看看以下两篇文章 👇
# 梳理前三个阶段

# 分层
- 生成图层树(Layer Tree)
- 拥有层叠上下文属性的元素会被提升为单独一层
- 需要裁剪(clip)的地方也会创建图层
- 图层绘制
首先需要知道的就是,浏览器在构建完布局树后,还需要进行一系列操作,这样子可能考虑到一些复杂的场景,比如一些些复杂的 3D 变换、页面滚动,或者使用 z-indexing 做 z 轴排序等,还有比如是含有层叠上下文如何控制显示和隐藏等情况。
# 生成图层树
你最终看到的页面,就是由这些图层一起叠加构成的,它们按照一定的顺序叠加在一起,就形成了最终的页面。
浏览器的页面实际上被分成了很多图层,这些图层叠加后合成了最终的页面。
我们来看看图层与布局树之间关系,如下图 👇

通常情况下,并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。
那什么情况下,渲染引擎会为特定的节点创建新图层呢?
有两种情况需要分别讨论,一种是显式合成,一种是隐式合成。
# 显式合成
# 一、 拥有层叠上下文的节点。
层叠上下文也基本上是有一些特定的 CSS 属性创建的,一般有以下情况:
HTML根元素,Canvas,Video,Document元素本身就具有层叠上下文,
普通元素设置position 不为 static并且设置了 z-index 属性,会产生层叠上下文。
元素的 transform 值不是 none
元素的 isolation 值是 isolate
**will-change**指定的属性值为上面任意一个。(will-change 的作用后面会详细介绍)
隐藏背面(backface-visibility: hidden)
倒影(box-reflect)
column-count(不为 auto)或者column-widthZ(不为 auto)
透明的(opacity 小于 1)、滤镜(filter)、遮罩(mask)、混合模式(mix-blend-mode 不为 normal)
对不透明度(opacity)、变换(transform)、滤镜(filter)应用动画
OverflowClipLayer
剪切溢出内容(overflow: hidden)
剪切路径(clip-path)
# 二、需要剪裁(clip)的地方。
比如一个标签很小,50*50 像素,你在里面放了非常多的文字,那么超出的文字部分就需要被剪裁。当然如果出现了滚动条,那么滚动条也会被单独提升为一个图层,如下图

数字 1 箭头指向的地方,可以看看,可能效果不是很明显,大家可以自己打开这个 Layers 探索下。
元素有了层叠上下文的属性或者需要被剪裁,满足其中任意一点,就会被提升成为单独一层。
# 隐式合成
这是一种什么样的情况呢,通俗意义上来说,就是z-index比较低的节点会提升为一个单独的途图层,那么层叠等级比它高的节点**都会**成为一个独立的图层。
缺点: 根据上面的文章来说,在一个大型的项目中,一个z-index比较低的节点被提升为单独图层后,层叠在它上面的元素统统都会提升为单独的图层,我们知道,上千个图层,会增大内存的压力,有时候会让页面崩溃。这就是层爆炸
# 绘制
完成了图层的构建,接下来要做的工作就是图层的绘制了。图层的绘制跟我们日常的绘制一样,每次都会把一个复杂的图层拆分为很小的**绘制指令,然后再按照这些指令的顺序组成一个绘制列表**,类似于下图 👇

从图中可以看出,绘制列表中的指令其实非常简单,就是让其执行一个简单的绘制操作,比如绘制粉色矩形或者黑色的线等。而绘制一个元素通常需要好几条绘制指令,因为每个元素的背景、前景、边框都需要单独的指令去绘制。
大家可以在 Chrome 开发者工具中在设置栏中展开 more tools, 然后选择Layers面板,就能看到下面的绘制列表:

在该图中,**箭头 2 指向的区域 **就是 document 的绘制列表,**箭头 3 指向的拖动区域 **中的进度条可以重现列表的绘制过程。
当然了,绘制图层的操作在渲染进程中有着专门的线程,这个线程叫做合成线程。
# 分块
- 接下来我们就要开始绘制操作了,实际上在渲染进程中绘制操作是由专门的线程来完成的,这个线程叫合成线程。
- 绘制列表准备好了之后,渲染进程的主线程会给合成线程发送
commit消息,把绘制列表提交给合成线程。接下来就是合成线程一展宏图的时候啦。
你想呀,有时候,你的图层很大,或者说你的页面需要使用滚动条,然后页面的内容太多,多的无法想象,这个时候需要滚动好久才能滚动到底部,但是通过视口,用户只能看到页面的很小一部分,所以在这种情况下,要绘制出所有图层内容的话,就会产生太大的开销,而且也没有必要。
- 基于上面的原因,合成线程会讲图层划分为图块(tile)
- 这些块的大小一般不会特别大,通常是 256 _ 256 或者 512 _ 512 这个规格。这样可以大大加速页面的首屏展示。
首屏渲染加速可以这么理解:
因为后面图块(非视口内的图块)数据要进入 GPU 内存,考虑到浏览器内存上传到 GPU 内存的操作比较慢,即使是绘制一部分图块,也可能会耗费大量时间。针对这个问题,Chrome 采用了一个策略: 在首次合成图块时只采用一个**低分辨率**的图片,这样首屏展示的时候只是展示出低分辨率的图片,这个时候继续进行合成操作,当正常的图块内容绘制完毕后,会将当前低分辨率的图块内容替换。这也是 Chrome 底层优化首屏加载速度的一个手段。
# 光栅化
接着上面的步骤,有了图块之后,合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图。
- 图块是栅格化执行的最小单位
- 渲染进程中专门维护了一个**栅格化线程池,专门负责把图块转换为位图数据**
- 合成线程会选择视口附近的图块(tile),把它交给栅格化线程池生成位图
- 生成位图的过程实际上都会使用 GPU 进行加速,生成的位图最后发送给合成线程
运行方式如下 👇

通常,栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫**快速栅格化**,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中。
相信你还记得,GPU 操作是运行在 GPU 进程中,如果栅格化操作使用了 GPU,那么最终生成位图的操作是在 GPU 中完成的,这就涉及到了跨进程操作。具体形式你可以参考下图:

从图中可以看出,渲染进程把生成位图的指令发送给 GPU,然后在 GPU 中执行指令,生成图块的位图,并保存在 GPU 的内存中。
# 合成和显示
栅格化操作完成后,合成线程会生成一个绘制命令,即"DrawQuad",并发送给浏览器进程。
浏览器进程中的viz组件接收到这个命令,根据这个命令,把页面内容绘制到内存,也就是生成了页面,然后把这部分内存发送给显卡,那你肯定对显卡的原理很好奇。
看了某博主对显示器显示图像的原理解释:
无论是 PC 显示器还是手机屏幕,都有一个固定的刷新频率,一般是 60 HZ,即 60 帧,也就是一秒更新 60 张图片,一张图片停留的时间约为 16.7 ms。而每次更新的图片都来自显卡的前缓冲区。而显卡接收到浏览器进程传来的页面后,会合成相应的图像,并将图像保存到后缓冲区,然后系统自动将
前缓冲区和后缓冲区对换位置,如此循环更新。
这个时候,心中就有点概念了,比如某个动画大量占用内存时,浏览器生成图像的时候会变慢,图像传送给显卡就会不及时,而显示器还是以不变的频率刷新,因此会出现卡顿,也就是明显的掉帧现象。
用一张图来总结 👇

我们把上面整个的渲染流水线,用一张图片更直观的表示 👇

# 回流-重绘-合成
更新视图三种方式
- 回流
- 重绘
- 合成
# 重排(回流/布局)
Render Object 在添加到树之后,还需要重新计算位置和大小;ComputedStyle 里面已经包含了这些信息,为什么还需要重新计算呢?因为像 margin: 0 auto; 这样的声明是不能直接使用的,需要转化成实际的大小,才能通过**绘图引擎**绘制节点;这也是 DOM Tree 和 CSSOM Tree 需要组合成 Render Object Tree 的原因之一。
布局是从 Root Render Object 开始递归的,每一个 Render Object 都有对自身进行布局的方法。
为什么需要递归(也就是先计算子节点再回头计算父节点)计算位置和大小呢?
因为有些布局信息需要子节点先计算,之后才能通过子节点的布局信息计算出父节点的位置和大小;
例如父节点的高度需要子节点撑起。如果子节点的宽度是父节点高度的 50%,要怎么办呢?这就需要在计算子节点之前,先计算自身的布局信息,再传递给子节点,子节点根据这些信息计算好之后就会告诉父节点是否需要重新计算。
重排触发的条件就是:对 DOM 结构的修改引发 DOM 几何尺寸变化的时候,会发生回流过程。
具体一点,有以下的操作会触发回流:
- 一个 DOM 元素的几何属性变化,常见的几何属性有
width、height、padding、margin、left、top、border等等, 这个很好理解。 - 使 DOM 节点发生增减或者移动。
- 读写
offset族、scroll族和client族属性的时候,浏览器为了获取这些值,需要进行回流操作。 - 调用
window.getComputedStyle方法。
一些常用且会导致回流的属性和方法:
clientWidth、clientHeight、clientTop、clientLeftoffsetWidth、offsetHeight、offsetTop、offsetLeftscrollWidth、scrollHeight、scrollTop、scrollLeftscrollIntoView()、scrollIntoViewIfNeeded()getComputedStyle()getBoundingClientRect()scrollTo()
依照上面的渲染流水线,触发回流的时候,如果 DOM 结构发生改变,则重新渲染 DOM 树,然后将后面的流程(包括主线程之外的任务)全部走一遍。

# 重绘
当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。
根据概念,我们知道由于没有导致 DOM 几何属性的变化,因此元素的位置信息不需要更新,从而省去布局的过程,流程如下:

跳过了**布局树**和建图层树,直接去绘制列表,然后在去分块,生成位图等一系列操作。
可以看到,重绘不一定导致回流,但回流一定发生了重绘。
# flush 队列
部分浏览器缓存了一个flush 队列,把我们触发的回流与重绘任务都塞进去,待到队列里的任务多起来,或者达到了一定的时间间隔,或者“不得已”的时候,再将这些任务一口气出队列,但是当我们访问一些及时属性时,浏览器会为了获得此时此刻、最准确的属性值,而提前将 flush 队列的任务出队列
# 合成
还有一种情况:就是更改了一个既不要布局也不要绘制的属性,那么渲染引擎会跳过布局和绘制,直接执行后续的合成操作,这个过程就叫合成。
举个例子:比如使用CSS的transform来实现动画效果,避免了回流跟重绘,直接在非主线程中执行合成动画操作。显然这样子的效率更高,毕竟这个是在非主线程上合成的,没有占用主线程资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率。
利用这一点好处:
- 合成层的位图,会交由
GPU(显卡) 合成,比CPU处理要快 - 当需要
repaint时,只需要repaint本身,不会影响到其他的层 - 对于
transform和opacity效果,不会触发layout和paint
提升合成层的最好方式是使用 CSS 的 will-change 属性
# GPU 加速原因
比如利用 CSS3 的transform、opacity、filter这些属性就可以实现合成的效果,也就是大家常说的**GPU 加速**。
- 在合成的情况下,直接跳过布局和绘制流程,进入非主线程处理部分,即直接交给合成线程处理。
- 充分发挥
GPU优势,合成线程生成位图的过程中会调用线程池,并在其中使用GPU进行加速生成,而GPU是擅长处理位图数据的。 - 没有占用主线程的资源,即使主线程卡住了,效果依然流畅展示。
# 实践意义
- 使用
createDocumentFragment进行批量的 DOM 操作 - 对于
resize、scroll等进行防抖/节流处理。 - 动画使用
transform或者opacity实现 - 将元素的
will-change设置为opacity、transform、top、left、bottom、right。这样子渲染引擎会为其单独实现一个图层,当这些变换发生时,仅仅只是利用合成线程去处理这些变换,而不牵扯到主线程,大大提高渲染效率。 - 对于不支持
will-change属性的浏览器,使用一个3D transform属性来强制提升为合成transform: translateZ(0); requestAnimationFrame优化等等
# 面试知识点
# Load 和 DOMContentLoaded 区别
Load 事件触发代表页面中的 DOM,CSS,JS,图片已经全部加载完毕。
DOMContentLoaded 事件触发代表初始的 HTML 被完全加载和解析,不需要等待 CSS,JS,图片加载。
# 图层(Layer)
一般来说,可以把普通文档流看成一个图层。特定的属性可以生成一个新的图层。不同的图层渲染互不影响,所以对于某些频繁需要渲染的建议单独生成一个新图层,提高性能。但也不能生成过多的图层,会引起反作用(层爆炸)。
通过以下几个常用属性可以生成新图层
3D变换:translate3d、translateZwill-changevideo、iframe标签- 通过动画实现的
opacity动画转换 position: fixed
# 重绘(Repaint)和回流(Reflow)
重绘和回流是渲染步骤中的一小节,但是这两个步骤对于性能影响很大。
- 重绘是当节点需要更改外观而不会影响布局的,比如改变
color就叫称为重绘 - 回流是布局或者几何属性需要改变就称为回流。
回流必定会发生重绘,重绘不一定会引发回流。回流所需的成本比重绘高的多,改变深层次的节点很可能导致父节点的一系列回流。
所以以下几个动作可能会导致性能问题:
- 改变 window 大小
- 改变字体
- 添加或删除样式
- 文字改变
- 定位或者浮动
- 盒模型
很多人不知道的是,重绘和回流其实和 Event loop 有关。
- 当
Event loop执行完Microtasks后,会判断document是否需要更新。因为浏览器是60Hz的刷新率,每16ms才会更新一次。 - 然后判断是否有
resize或者scroll,有的话会去触发事件,所以resize和scroll事件也是至少16ms才会触发一次,并且自带节流功能。 - 判断是否触发了
media query - 更新动画并且发送事件
- 判断是否有全屏操作事件
- 执行
requestAnimationFrame回调 - 执行
IntersectionObserver回调,该方法用于判断元素是否可见,可以用于懒加载上,但是兼容性不好 - 更新界面
- 以上就是一帧中可能会做的事情。如果在一帧中有空闲时间,就会去执行
requestIdleCallback回调。
以上内容来自于 HTML 文档 (opens new window)
# 减少重绘和回流
使用
translate替代top<div class="test"></div> <style> .test { position: absolute; top: 10px; width: 100px; height: 100px; background: red; } </style> <script> setTimeout(() => { // 引起回流 document.querySelector('.test').style.top = '100px' }, 1000) </script> 复制代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17使用
visibility替换display: none,因为前者只会引起重绘,后者会引发回流(改变了布局)把 DOM 离线后修改,比如:先把 DOM 给
display:none(有一次 Reflow),然后你修改 100 次,然后再把它显示出来不要把 DOM 结点的属性值放在一个循环里当成循环里的变量
for (let i = 0; i < 1000; i++) { // 获取 offsetTop 会导致回流,因为需要去获取正确的值 console.log(document.querySelector('.test').style.offsetTop) } 复制代码1
2
3
4
5不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局
动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用
requestAnimationFrameCSS选择符从右往左匹配查找,避免DOM深度过深将频繁运行的动画变为图层,图层能够阻止该节点回流影响别的元素。比如对于
video标签,浏览器会自动将该节点变为图层。
# 行内样式和 CSSStyleSheet 的区别
每个 link引入的CSS 文件或嵌入样式(style 标签)都会对应一个 CSS StyleSheet 对象
这个对象由一系列的 Rule(规则) 组成;每一条 Rule 都会包含 Selectors(选择器) 和若干 Declearation(声明),Declearation 又由 Property(属性)和 Value(值)组成。
另外,浏览器默认样式表(defaultStyleSheet)和用户样式表(UserStyleSheet)也会有对应的 CSSStyleSheet 对象,因为它们都是单独的 CSS 文件。
至于行内样式,在构建 DOM Tree 的时候会直接解析成 Declearation 集合挂载到元素的 style 属性上。
所有的 CSS StyleSheet 都挂载在 document 节点上,我们可以在浏览器中通过 document.styleSheets 获取到这个集合。行内样式可以直接通过节点的 style 属性查看。
通过一个例子,来了解下行内样式和 CSSStyleSheet 的区别:
--------demo.html---------
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<style>
body .div1 {
line-height: 1em;
}
</style>
<link rel="stylesheet" href="./style.css" />
<style>
.div1 {
background-color: #f0f;
height: 20px;
}
</style>
<title>Document</title>
</head>
<body>
<div class="div1" style="font-size: 12px;">test</div>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
----------style.css--------
@import "./index.css";
.div1{
background-color: #fff;
}
2
3
4
5
----------index.css---------- .div1 {
height: 80px;
}
2
3
可以看到一共有三个 CSSStyleSheet 对象,每个 CSSStyleSheet 对象的 rules 里面会有一个 style 属性,style 属性的值就是 CSSStyleDeclaration,而行内样式获取到的直接就是 CSSStyleDeclaration。
(在一个css文件中通过@import引入另一个css文件,会合并成一个CSS StyleSheet)

