渲染流程
浏览器加载一份html文当是怎么加载的?
1.解析html中的标签,文本,注释,属性等信息,自上而下将所有节点包括用JS动态创建并添加的节点遍历完后就构建了文档树(DOM Tree)
2.解析DOM Tree中的节点时遇到了不同的元素会有不同的操作:
2.1解析CSS样式,浏览器自带的样式设置和标签内嵌的样式,用link标签引用的外部CSS以及用style标签写的嵌入式样式,最终都会转到styleSheets中,就构建了CSS样式结构体。
2.2遇到了src会去加载然后网络请求资源
3.把CSS样式结构体和DOM Tree结合变成呈现树/渲染树(Render Tree)
4.按照Render Tree绘制页面进行布局。
render tree有点类似于dom tree,但有区别,render tree能识别样式。render tree中每个节点都有自己的style,而且render tree不包含隐藏的节点,比如display:none的节点,还有head节点。因为这些节点不会用于呈现,而且不会影响呈现的,所以就不会包含到render tree中。但是visibility:hidden隐藏的元素以及元素透明度为0的还是会包含到render tree中的,因为visibility:hidden会影响布局,会占有位置。
渲染树(render tree)中的节点,文档树(dom tree)中一定有;dom tree中有的节点,render tree不一定有。
重绘与回流
重绘:节点的改变不会影响布局,更新了元素的绘制属性。如改变了背景颜色,字体颜色和等不会影响布局和元素的隐藏没有影响布局。
回流:节点的改变会影响页面布局,更新了元素的几何属性。如改变了元素的宽高以及元素在文档树中的位置和元素的数量和元素的隐藏影响了布局等。
1.回流必然引起重绘,但是重绘不一定引起回流
2.每个页面至少需要一次回流,就是在页面第一次加载的时候。
常见的回流和重绘操作
任何对render tree中元素的操作都会引起回流或者重绘
1)添加、删除元素(回流+重绘)
2)隐藏元素,display:none(回流+重绘);visibility:hidden(只重绘,不回流)
3)移动元素,比如改变top,left或者移动元素到另外1个父元素中。(重绘+回流)
4)对style的操作(对不同的属性操作,影响不一样)
5)还有一种是用户的操作,比如改变浏览器大小,改变浏览器的字体大小等(回流+重绘)
6)操作元素的innerHTML会引起回流
优化方案避免重绘与回流
优化原因:程序执行时,常常会操作页面就会引起重绘和回流。频繁的重绘和回流会造成页面性能不好比如页面卡顿、迟缓,手机发烫。重绘与回流操作的次数越多,计算机的性能消耗越大。
因此操作页面时: 尽量避免高频重绘或者使用框架
避免高频回流案例
<style>
#box td {
border: 1px gainsboro solid;
}
</style>
<table id="box">
</table>
<script>
// 添加1万个格子到页面上 每个格子显示时间(ms)
let tb = document.querySelector("#box");
for (let i = 0; i < 100; i++) {
let tr = document.createElement("tr");
tb.appendChild(tr);
for (let j = 0; j < 100; j++) {
let dt = new Date().getTime();
let td = document.createElement("td");
td.innerHTML = dt;
tr.appendChild(td);
};
};
</script>
这样就回流了1万零100次,创建了10000个单元格添加到页面,又创建了100行单元行添加到页面,它们都影响了页面的布局,就回流了1万零100次。
创建DocumentFragment元素,DocumentFragment是一个文档片段,可以保存将来可能会添加到文档中的节点,但不会像完整的文档那样占用额外的资源。
用JS的DOM创建很多节点时,在加入节点到DOM树上时,节点需要一个个渲染,这样节点数较多时就会影响浏览器的渲染效率,这个时候我们将创建的节点都放在DocumentFragment这样的节点上然后把DocumentFragment加入至DOM,只需要完成一次渲染就可以达到之前很多次渲染的效果,就避免了高频回流。
<style>
#box td {
border: 1px gainsboro solid;
}
</style>
<table id="box">
</table>
<script>
let tb=document.querySelector("#box");
let fra1=document.createDocumentFragment();
for(let i=0;i<100;i++){
let tr=document.createElement("tr");
fra1.appendChild(tr);
for(let j=0;j<100;j++){
let dt=new Date().getTime();
let td=document.createElement("td");
td.innerHTML=dt;
tr.appendChild(td);
};
};
tb.appendChild(fra1);
</script>
这样就只回流了1次,在把DocumentFragment加入至DOM时。
//dom=>fragment
//wx=>block
//vue=>template
//react=></>