Fork me on GitHub

《浏览器基础》之重绘与重排

概览

重绘和重排都是发生在浏览器呈现引擎(渲染引擎)中的由于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选择器简单,直观(即使你使用的是预处理程序),越少嵌套越好。选择器的效率排名如下(第一个是最快的):
    1. Identificator: #id
    2. Class: .class
    3. Tag: div
    4. Neighbour selector: a + i
    5. Children selector: ul > li
    6. Universal selector: * . Attribute selector: inputtype=”text”
    7. Pseudoelements and pseudoclasses: a:hover
  • 尽量减少对DOM的操作,如果你对某个属性和对象要进行多次操作,要缓存它们。如果要完成复杂操作的时候,可以多用离线存储技术。
  • 最好只用.class设置元素的样式。
  • 所有的动画都设置为fixed或者absolute定位(要不就不要用动画)。
  • 重排是一个非常昂贵的操作,尽可能多的减少重排的次数和重排的影响范围。
  • 当页面滚动的时候禁用所有的:hover。

参考文章

浏览器的工作原理:新式网络浏览器幕后揭秘
Rendering: repaint, reflow/relayout, restyle

posted @ 2016-10-01 14:49  Terry√  阅读(1278)  评论(0编辑  收藏  举报