《浏览器基础》之重绘与重排
概览
重绘和重排都是发生在浏览器呈现引擎(渲染引擎)中的由于DOM元素改变所作出的事件过程。重绘不一定引起重排,而重排一定引起重绘。
重绘:(Repaint) 有的资料也被叫做改型( Restyling),是由于元素的外观样式属性的改变所触发的行为,如visibility、背景颜色,边框颜色等属性。
重排:(Reflow)也被成为重新布局(Relayout),是由于元素的结构属性(或者说是几何属性)改变所触发的行为,主要场景如下:
- DOM树节点的操作比, 如:Resizing, Removing, Adding 。
- 文本的改变,如:text。
- 浏览器窗口的改变,如: Resizing, Scrolling。
- 伪类的激活,如::hover。
- Class 属性改变。
- Css 属性改变。
- 新的stylesheets 被添加或者旧的被删除。
浏览器对重排的优化:
通常浏览器对重绘和重排做了一些优化处理,比如:
-
如果改变一个absolute或者 fixed定位的元素,那么只会重排这个元素和他的子元素,但是当改变了static定位的元素,那么整个页面都会重排。
-
另外比较有趣的是,
如下只也会引起一次重绘和一次重排:var $body = $('body');
$body.css('padding', '1px'); //no reflow, repaint
$body.css('color', 'red'); //no repaint
$body.css('margin', '2px'); // reflow, repaint
如上描述,并不是每次属性改变都会引起重排或重绘,有时,它会等一段代码执行结束再一起处理,这样就只发生一次重排。但是如果直接获取属性值,浏览器会强制发生重排来确保获取真实的值。如下:
var $body = $('body');
$body.css('padding', '1px');
$body.css('padding'); // forced reflow
$body.css('color', 'red');
$body.css('margin', '2px');
总共我们得到了两此重排,由此看出浏览器的优化失败了。So 如果你想更改一些属性并且想使他们获得如你所愿的性能的时候,可以一起执行修改,然后再获取属性。具体参照下如下的代码:
<!DOCTYPE html\>
<html>
<head>
<script src="http://code.jquery.com/jquery-2.1.0.min.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
<style type="text/css">
.block {
padding: 50px;
margin: 10px;
background: #ccc;
}
</style>
<script type="text/javascript">
$(function() {
var $body = $('body');
// 1 reflow
$body.on('click', '.block-1', function(e) {
$body.css('padding', '1px');
$body.css('color', 'red');
$body.css('margin', '2px');
})
// 2 reflows
.on('click', '.block-2', function(e) {
$body.css('padding', '1px');
$body.css('padding');
$body.css('color', 'red');
$body.css('margin', '2px');
})
// 3 repaints
.on('click', '.block-3', function(e) {
$body.css('color', 'red');
$body.css('color');
$body.css('color', 'yellow');
$body.css('background');
$body.css('color', 'blue');
$body.css('outline');
})
// 1 repaint
.on('click', '.block-4', function(e) {
$body.css('color', 'red');
$body.css('color', 'yellow');
$body.css('color', 'blue');
$body.css('color');
$body.css('background');
$body.css('outline');
})
// 3 reflows
.on('click', '.block-5', function(e) {
$body.css('padding', '1px');
$body[0].offsetHeight;
$body.css('padding', '2px');
$body[0].offsetTop;
$body.css('padding', '3px');
$body[0].offsetWidth;
})
// 1 reflow
.on('click', '.block-6', function(e) {
$body.css('padding', '1px');
$body.css('padding', '2px');
$body.css('padding', '3px');
$body[0].offsetHeight;
$body[0].offsetTop;
$body[0].offsetWidth;
});
});
</script>
</head>
<body>
<div class="block block-1">Click to run example #1 (1 reflow)</div>
<div class="block block-2">Click to run example #2 (2 reflow)</div>
<div class="block block-3">Click to run example #3 (3 repaint)</div>
<div class="block block-4">Click to run example #4 (1 repaint)</div>
<div class="block block-5">Click to run example #5 (3 reflow)</div>
<div class="block block-6">Click to run example #6 (1 reflow)</div>
</body>
</html>
有时我们不能避免重排的发生。比如,我们需要设置两次margin-left。第一次设置它100px(没有动画),第二次设置为50px(有动画)。代码如下:
<!DOCTYPE html\>
<html>
<head>
<title></title>
<script type="text/javascript">
$('.example-1 li').click(function(){
$(this).removeClass('has-transition');
$(this).css('margin-left', 100);
$(this).addClass('has-transition');
$(this).css('margin-left', 50);
});
$('.example-2 li').click(function(){
$(this).removeClass('has-transition');
$(this).css('margin-left', 100);
$(this)[0].offsetHeight; // 强制执行重排,确保设置的100px能够生效
$(this).addClass('has-transition');
$(this).css('margin-left', 50);
});
</script>
<style type="text/css">
.has-transition {
-webkit-transition: margin-left 1s ease-out;
-moz-transition: margin-left 1s ease-out;
-o-transition: margin-left 1s ease-out;
transition: margin-left 1s ease-out;
}
li {
background: #ccc;
border: 1px #000 solid;
display: block;
padding: 2px;
margin-left: 0;
margin-top: 4px;
margin-bottom: 4px;
}
</style>
</head>
<body>
<p>第一种情况)</p>
<ul class="example-1">
<li class="has-transition">1</li>
<li class="has-transition">2</li>
<li class="has-transition">3</li>
<li class="has-transition">4</li>
<li class="has-transition">5</li>
</ul>
<p>第二种情况)</p>
<ul class="example-2">
<li class="has-transition">1</li>
<li class="has-transition">2</li>
<li class="has-transition">3</li>
<li class="has-transition">4</li>
<li class="has-transition">5</li>
</ul>
</body>
</html>
如上,因为浏览器的缓存的原因只会在脚本的末尾才重排的特性,第一种方案是无法满足需求的,而我们需要有一个重排所以加上一个获取属性的代码$(this)[0].offsetHeight;\
主动调用重排,也就是第二方案就是我们要的效果。
最后是我最近工作中总结的一些关于前端优化的小技巧:
- 在<head>标签内引用样式,在<body>标签后面引用scripts
- 尽量让css选择器简单,直观(即使你使用的是预处理程序),越少嵌套越好。选择器的效率排名如下(第一个是最快的):
- Identificator: #id
- Class: .class
- Tag: div
- Neighbour selector: a + i
- Children selector: ul > li
- Universal selector: * . Attribute selector: inputtype=”text”
- Pseudoelements and pseudoclasses: a:hover
- 尽量减少对DOM的操作,如果你对某个属性和对象要进行多次操作,要缓存它们。如果要完成复杂操作的时候,可以多用离线存储技术。
- 最好只用.class设置元素的样式。
- 所有的动画都设置为fixed或者absolute定位(要不就不要用动画)。
- 重排是一个非常昂贵的操作,尽可能多的减少重排的次数和重排的影响范围。
- 当页面滚动的时候禁用所有的:hover。
参考文章
浏览器的工作原理:新式网络浏览器幕后揭秘
Rendering: repaint, reflow/relayout, restyle