文章目录
1.一个页面从输入URL到加载显示完成,这个过程发生了什么?
https://segmentfault.com/a/1190000014872028?utm_source=sf-related
- 输入url地址
- 域名解析(将域名解析为IP地址):查看浏览器DNS缓存==>本地DNS缓存==>路由器缓存==>ISP缓存(互联网服务提供商)>根域名服务器>顶级域名服务器==>权威域名服务器
- 通过三次握手建立TCP/IP连接
- 发送http请求
- 服务器处理请求并返回HTTP报文
- 浏览器解析渲染页面
- 网页的解析是交给渲染进程中的GUI渲染线程处理的。
- HTML解释器会将解析生成一棵DOM树,CSS解释器会将css代码解析生成为一棵CSSOM树。
- 在解析的过程中,如果遇到js代码,则会将执行权交给js引擎线程处理,因为js引擎线程与GUI线程是互斥的,所以js的加载会阻塞页面的渲染。
- 生成DOM树和CSSOM树之后,遍历DOM树的每一个可见的节点,对于每个可见的节点,找到CSSOM树中对应的规则,然后组合在一起,最终生成一棵渲染树。(注意:渲染树只包含可见的节点)
- Layout(回流):根据生成的渲染树,得到每个节点的位置,大小。
- Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素。
- 最后将像素发送给GPU,展示在页面上。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OA3Fsg6i-1601978311602)(https://raw.githubusercontent.com/Vacricticy/myPicBed/master/img/image-20201004222347768.png)]
2. 重绘和重排(回流)
https://segmentfault.com/a/1190000017329980
(1)渲染树的生成过程:
在生成DOM树和CSSOM树之后,会遍历DOM树的每一个可见的节点,对于每个可见的节点,找到CSSOM树中对应的规则,然后组合在一起,最终生成一棵渲染树。(注意:渲染树只包含可见的节点)
这里可见的节点不包括:
- script、meta、link等节点
- 以及通过css进行隐藏的节点。比如display:none。注意,利用visibility和opacity隐藏的节点,还是会显示在渲染树上的。只有display:none的节点才不会显示在渲染树上。
(2)回流:
依据渲染树,根据视口(viewport)的大小来计算元素的几何信息(位置,大小)的,这个计算的阶段就是回流(layout),也称为重排。
(3)重绘:
从渲染树身上,可以得到节点的样式;
在回流的结果中,得到了节点的位置和大小。
借助这两个东西就可以将渲染树的每个节点都转换为屏幕上的实际像素,这一过程就叫重绘(painting)
(4)何时会触发回流和重绘
- 页面首次渲染
- 添加或删除可见的DOM元素
- 元素的位置和大小发生变化
- 内容发生变化(文字数量或图片大小等等)
- 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)
- 使用offset,client,scroll系列属性时
- 激活CSS伪类
- 设置style属性
什么情况会触发整个页面的重排:滚动条出现的时候或者修改了根节点。
回流一定会触发重绘,但重绘不一定会回流。
不触发重排,但会触发重绘的情况:改变元素的外观属性,比如color,background-color。
(5)浏览器对于重排的优化机制:
为了减少重排的计算消耗,浏览器会将修改操作放入到一个队列里,直到过了一段时间或者操作达到了一个阈值,才清空队列,触发重排。
注意:当我们获取offset,cilent,scroll系列属性时,获取的是最新的布局信息,因此此时浏览器必须清空队列触发回流和重绘来获取最新的属性值。所以,在修改样式的时候,最好避免使用这些属性,或者是将这些值缓存起来。
(6)减少重绘和重排:
a.合并多次对DOM样式的修改:
//每次设置style属性都会触发一次重排
const el = document.getElementById('test');
el.style.padding = '5px';
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
//优化方案1:使用cssText
const el = document.getElementById('test');
el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px;';
//优化方案2:修改css的class类
const el = document.getElementById('test');
el.className += ' active';
b.批量修改DOM:
当我们需要对DOM进行一系列修改的时候,可以通过以下原则减少回流重绘次数:
- 使元素脱离文档流
- 对其进行多次修改
- 将元素带回到文档中。
该过程的第一步和第三步可能会引起回流,但是经过第一步之后,对DOM的所有修改都不会引起回流,因为它已经不在渲染树了。
具体的实现有下面三种方式:
- 隐藏元素,应用修改,重新显示
- 使用文档片段(document fragment)在当前DOM之外构建一个子树,再把它拷贝回文档。
- 将原始元素拷贝到一个脱离文档的节点中,修改节点后,再替换原始的元素。
<body>
<ul id="ul"></ul>
</body>
<script>
let data = [
{
name: "liu",
},
{
name: "haha",
},
{
name: "adele",
},
{
name: "adele",
},
{
name: "adele",
},
];
//原方法会触发多次重排
for (var i = 0; i < data.length; i++) {
var li = document.createElement("li");
li.innerHTML = data[i].name;
ul.appendChild(li);
}
//核心就是不能直接在DOM树上进行所有操作
//方式1:隐藏元素,修改元素,显示元素
ul.style.display = "none";
for (var i = 0; i < data.length; i++) {
var li = document.createElement("li");
li.innerHTML = data[i].name;
ul.appendChild(li);
}
ul.style.display = "block";
//方式2:通过createDocumentFragment API创建一个文档片段(该文档片段不属于DOM树),然后修改文档片段,最后将其附加到DOM树中
const fragment = document.createDocumentFragment();
data.forEach((item) => {
var li = document.createElement("li");
li.textContent = item.name;
fragment.appendChild(li);
});
ul.appendChild(fragment);
//方式3:通过element.cloneNode()克隆一个节点,克隆出的节点不属于DOM树的一部分,在该结点修改后将原节点替换掉。
let clone = ul.cloneNode(true); //true表示深度拷贝
data.forEach((item) => {
var li = document.createElement("li");
li.textContent = item.name;
clone.appendChild(li);
});
ul.parentNode.replaceChild(clone, ul);
</script>
c.缓存布局信息,减少某些属性的使用:
当我们获取offset,cilent,scroll系列属性时,获取的是最新的布局信息,因此此时浏览器必须清空队列触发回流和重绘来获取最新的属性值。所以,在修改样式的时候,最好避免使用这些属性,或者是将这些值缓存起来。
for (var i = 0; i < 50000; i++) {
box1.style.height = box2.offsetHeight +i+ "px";
}
//浏览器会等队列到达一定的阈值才触发重排
var height=box2.offsetHeight;
for (var i = 0; i < 50000; i++) {
box1.style.height = height +i+ "px";
}
d.对于复杂的动画效果,应使用绝对定位让其脱离文档流:
e.CSS3硬件加速
当触发CSS3硬件加速时,不会引起回流重绘现象。
常见的触发硬件加速的css属性:
- transform
- opacity
- filters
如果你为太多元素使用css3硬件加速,会导致内存占用较大,会有性能问题
f.不要使用table表格:
table表格中某个元素触发重排时,会导致所有元素都触发重排,严重影响效率。