前端面试中“页面的回流与重绘”是性能优化方面经常考察的一个知识点。今天总结一下,有不对或者不全面的地方欢迎指正~
1
在讨论页面重绘、回流之前,需要对页面的呈现流程有些了解,页面是怎么把HTML结合CSS等显示到浏览器上的,下面的流程图显示了浏览器对页面呈现的处理流程。可能不同的浏览器略微会有些不同,但基本上都是类似的。
1
浏览器把获取到的HTML代码解析成1颗Dom树,HTML中的每个Tag都是Dom树中的1个节点,根节点就是我们常用的Document对象。Dom树里包含了所有HTML标签,包括display:none隐藏,还有用JS动态添加的元素等。
2
浏览器把所有样式解析成样式结构体,在解析的过程中会去掉浏览器不能识别的样式,比如IE会去掉-moz开头的样式,而FF会去掉_开头的样式。
3
Dom树和样式结构体组合后构建Render树, Render树类似于Dom 树,但区别很大,Render树能识别样式,Render树中每个节点都有自己的style,而且Render树不包含隐藏的节点 (比如display:none的节点,还有head节点),因为这些节点不会用于呈现,而且不影响其它节点呈现,所以就不会包含到 Render树中。注意 visibility:hidden隐藏的元素还是会包含到 Render树中的,因为visibility:hidden 会影响布局(layout),会占有空间。
4
一旦Render树构建完毕后,浏览器就可以根据Render树来绘制页面了。
2
当Render树中的一部分(或全部)因为元素的规模尺寸、布局、显隐等改变而需要重新构建,这就称为回流。
当Render树中的一些元素需要更新属性,而这些属性只是影响元素的外观、风格,不影响布局,比如background-color,就称为重绘。
注意:回流必将引起重绘,而重绘不一定会引起回流。
以上,其实理解起来很容易,所谓的Render树就是识别了几何属性的Dom树,好像我们画人体的时候,Dom树是先确定都有什么,比如四肢,头部,身体,其他器官等;而Render树则是确定这个人的高矮胖瘦,头发是否盖眼睛等。如果我们在绘画过程中发现脖子长了那就惨了,脖子下面都要重画,如果发现只是手指画的有问题,我们只需要重画手指,这就是回流了。当我们的Render树完事了,也就是人体大概轮廓我们都画好了,就可以上色了,换个发色这种我们叫重绘。
举个栗子🌰:
var s = document.body.style;
s.padding = "2px"; // 回流+重绘
s.border = "1px solid red"; // 再一次 回流+重绘
s.color = "blue"; // 再一次重绘
s.backgroundColor = "#ccc"; // 再一次 重绘
s.fontSize = "14px"; // 再一次 回流+重绘
// 添加node,再一次 回流+重绘
document.body.appendChild(document.createTextNode('abc!'));
3
当页面布局和几何属性改变时就需要回流,下述情况会发生浏览器回流:
页面渲染初始化
添加或者删除可见的DOM元素;
DOM元素的几何属性变。
内容改变。
浏览器窗口尺寸改变。
获取某些属性
1、页面渲染初始化
每个页面至少需要一次回流,就是在页面第一次加载的时候。
2、添加或者删除可见的DOM元素
当DOM树的结构变化时,例如节点的增减、移动等,也会触发回流。浏览器引擎布局的过程,类似于树的前序遍历,是一个从上到下从左到右的过程。通常在这个过程中,当前元素不会再影响其前面已经遍历过的元素。
3、DOM元素的几何属性变化
当DOM元素的几何属性变化时,渲染树中的相关节点就会失效,浏览器会根据DOM元素的变化重建构建渲染树中失效的节点。当前元素的回流通常会带来一系列的反应,甚至触发整个文档的回流和重绘,性能代价是高昂的。
4、内容改变
文本改变或者图片大小改变而引起的计算值宽度和高度改变。
5、浏览器窗口尺寸改变
resize事件发生时。
6、获取某些属性
虽然浏览器引擎可能会针对回流做了优化,比如Opera,它会等到有足够数量的变化发生,或者等到一定的时间,或者等一个线程结束,再一起处理,这样就只发生一次回流。但除了render树的直接变化,当获取一些属性时,浏览器为取得正确的值也会触发回流。这些属性包括:offsetTop/tLeft/Width/Height、scrollTop/Left/Width/Height、 clientTop/Left/Width/Height、width、height,请求了getComputedStyle(), 或者 IE的 currentStyle也会引起回流。
4
减少回流、重绘其实就是需要减少对Render树的操作(合并多次多Dom和样式的修改),并减少对一些style信息的请求,尽量利用好浏览器的优化策略。具体方法有:
1、在内存中多次操作节点,完成后再添加到文档中去。
例如要异步获取表格数据,渲染到页面。可以先取得数据后在内存中构建整个表格的html片段,再一次性添加到文档中去,而不是循环添加每一行。
2、将那些改变样式的操作集合在一起,直接改变className。如果动态改变样式,则使用cssText。
栗🌰1:
js 代码:
var changeDiv = document.getElementById('changeDiv');
changeDiv.style.color = '#093';
changeDiv.style.background = '#eee';
可以合并为:
css 代码:
div.changeDiv {
background: #eee;
color: #093;
height: 200px;
}
js 代码:
document.getElementById('changeDiv').className = ‘changeDiv’;
栗子🌰2:
js 代码:
// 不好的写法
var left = 1;
var top = 1;
el.style.left = left + "px";
el.style.top = top + "px";
// 比较好的写法
el.style.cssText += ";
left: " + left + "px;
top: " + top + “px;";
3、将需要多次重排的元素,脱离文档流。
position属性设为absolute或fixed,这样此元素就脱离了文档流,它的变化不会影响到其他元素。
4、尽量不要使用表格布局****。
如果没有定宽表格一列的宽度由最宽的一列决定,那么很可能在最后一行的宽度超出之前的列宽,引起整体回流造成table可能需要多次计算才能确定好其在渲染树中节点的属性,通常要花3倍于同等元素的时间。
6、让要操作的元素进行”离线处理”,处理完后一起更新。
a) 使用DocumentFragment进行缓存操作,引发一次回流和重绘;
b) 使用display:none,只引发两次回流和重绘;
c) 使用cloneNode(true or false) 和 replaceChild 技术,引发一次回流和重绘;
** 7、在需要经常取那些引起浏览器重排的属性值时,要缓存到变量。**
// 别这样写
for(循环) {
el.style.left = el.offsetLeft + 5 + "px";
el.style.top = el.offsetTop + 5 + "px";
}
// 这样写好点
var left = el.offsetLeft,
top = el.offsetTop,
s = el.style;
for (循环) {
left += 10;
top += 10;
s.left = left + "px";
s.top = top + "px";
}
最后再强调一下,以上内容参考了网上的资料,如有雷同,那很正常。。。
始发于微信公众号: 前端麻辣烫