浏览器在渲染页面的时候,大致是以下几个步骤:
1、解析html生成DOM树,解析css,生成CSSOM树,将DOM树和CSSOM树结合,生成渲染树;
2、根据渲染树,浏览器可以计算出网页中有哪些节点,各节点的CSS以及从属关系 - 回流
3、根据渲染树以及回流得到的节点信息,计算出每个节点在屏幕中的位置 - 重绘
4、最后将得到的节点位置信息交给浏览器的图形处理程序,让浏览器中显示页面
回流:英文叫reflow,指的是当渲染树中的节点信息发生了大小、边距等问题,需要重新计算各节点和css具体的大小和位置。例:在css中对一个div修饰的样式中,使用了宽度50%,此时需要将50%转换为具体的像素,这个计算的过程,就是回流的过程。
容易造成回流的css
width
height
padding
border
margin
position
top
left
bottom
right
float
clear
text-align
vertical-align
line-height
font-weight
font-size
font-family
overflow
white-space
重绘:英文叫repaint,当节点的部分属性发生变化,但不影响布局,只需要重新计算节点在屏幕中的绝对位置并渲染的过程,就叫重绘。比如:改变元素的背景颜色、字体颜色等操作会造成重绘。回流的过程在重绘的过程前面,所以回流一定会重绘,但重绘不一定会引起回流。
容易造成重绘的css
color
border-style
border-radius
text-decoration
box-shadow
outline
background
每次回流都会对浏览器造成额外的计算消耗,所以浏览器对于回流和重绘有一定的优化机制。浏览器通常都会将多次回流操作放入一个队列中,等过了一段时间或操作达到了一定的临界值,然后才会挨个执行,这样能节省一些计算消耗。但是在获取布局信息操作的时候,会强制将队列清空,也就是强制回流,比如访问或操作以下或方法时:
offsetTop
offsetLeft
offsetWidth
offsetHeight
scrollTop
scrollLeft
scrollWidth
scrollHeight
clientTop
clientLeft
clientWidth
clientHeight
getComputedStyle()
这些属性或方法都需要得到最新的布局信息,所以浏览器必须去回流执行。因此,在项目中,尽量避免使用上述属性或方法,如果非要使用的时候,也尽量将值缓存起来,而不是一直获取。
减少造成回流的次数,如果要给一个节点操作多个css属性,而每一个都会造成回流的话,尽量将多次操作合并成一个,例:
<script>
let oDiv = document.querySelector('.box');
oDiv.style.padding = '5px';
oDiv.style.border = '1px solid #000';
oDiv.style.margin = '5px';
</script>
操作div的3个css属性,分别是padding、border、margin,此时就可以考虑将多次操作合并为一次。
<script>
let oDiv=document.querySelector('.box');
oDiv.style.cssText='padding:5px;border:1px solid #000;margin:5px;';
</script>
<style>
.pbm{
padding:5px;
border:1px solid #000;
margin:5px;
}
</style>
<script>
let oDiv = document.querySelector('.box');
oDiv.classList.add('pbm');
</script>
当对DOM有多次操作的时候,需要使用一些特殊处理减少触发回流,其实就是对DOM的多次操作,在脱离标准流后,对元素进行的多次操作,不会触发回流,等操作完成后,再将元素放回标准流
脱离标准流的操作有以下3中:
例:下面对DOM节点的多次操作,每次都会触发回流
<script>
let data = [
{
id:1,
name:"商品1",
},
{
id:2,
name:"商品2",
},
{
id:3,
name:"商品3",
},
{
id:4,
name:"商品4",
},
];
let oUl = document.querySelector("ul");
for(let i=0;i<data.length;i++){
let oLi = document.createElement("li");
oLi.innerText = data[i].name;
oUl.appendChild(oLi);
}
</script>
这样每次给ul中新增一个li的操作,每次都会触发回流。
<script>
let oUl = document.querySelector("ul");
oUl.style.display = 'none';
for(let i=0;i<data.length;i++){
let oLi = document.createElement("li");
oLi.innerText = data[i].name;
oUl.appendChild(oLi);
}
oUl.style.display = 'block';
</script>
此时,在隐藏ul和显示ul的时候,触发了两次回流,给ul添加每个li的时候没有触发回流。
<script>
let oUl = document.querySelector("ul");
let fragment = document.createDocumentFragment();
for(let i=0;i<data.length;i++){
let oLi = document.createElement("li");
oLi.innerText = data[i].name;
fragment.appendChild(oLi);
}
oUl.appendChild(fragment);
</script>
<script>
let oUl = document.querySelector("ul");
let newUL = oUl.cloneNode(true);
for(let i=0;i<data.length;i++){
let oLi = document.createElement("li");
oLi.innerText = data[i].name;
newUL.appendChild(oLi);
}
oUl.parentElement.replaceChild(newUl, oUl);
</script>
关注通哥每周学点前端小知识,微信公众号搜索《每周小知识》。
评论
全部评论
{{reply.username}} 回复:{{reply.replyname}}
{{reply.content}}