为了账号安全,请及时绑定邮箱和手机立即绑定

布局与重排

布局

在进行 DOM 节点和样式规则匹配的同时,渲染引擎会为每一个可视节点创建 LayoutObject 对象。LayoutObject 对象保存了该节点所需要的布局信息,比如位置( 包含块模型 )、大小( 盒子模型 )等等。整个计算流程大致可以分为以下 4 步:

  1. 判断该节点是否需要重新布局,如果需要,创建 LayoutObject 对象;
  2. 计算该节点的宽度和垂直方向的外边距,更新 LayoutObject 对象;
  3. 遍历该节点的每一个子节点,依次计算它们的布局;
  4. 根据子节点的布局结果,计算该节点的高度,并最终返回 LayoutObject 对象。

*节点的高度计算,会依据节点类型的不同而采用不同的算法,例如块级元素,定义了自身的宽高,那么就可以据此来确定节点的高度( 期间可能还涉及到溢出时的展示效果 );而内联元素,则需要根据子节点的的大小来确定节点的高度。因此,布局计算通常会采用先确定宽度,再根据子节点的计算结果,确定高度的顺序进行。

渲染引擎的实际布局计算过程,远比我列举的 4 步更为复杂。至于如何做到最高效的计算,这是留给引擎开发者的终极任务,作为前端开发者,简单理解:DOM + CSSOM = LayoutObject

例子:

<style>
    body {
        margin: 0;
        padding: 5px;
    }
    
    div {
        width: 40px;
        height: 40px;
        margin: 5px;
    }
    
    span {
        font-size: 16px;
    }
</style>

<body>
    <div></div>
    <span>hello</span>
</body>

例子中 DOM 和 CSSOM 结合之后,创建出以下 LayoutObject:

body + { width:764px; height: 60px; } = LayoutBlock{ BODY } at( 0,0 ) size( 764*60 )
div + { width:40px; height: 40px; } = LayoutBlock{ DIV } at( 5,5 ) size( 50*50 )
span + { width:37px; height: 20px; } = LayoutInline{ SPAN } at( 5,55 ) size( 37*20 )

*例子中没有设定位置的 CSS 样式,因此全部按照正常流去理解定位。节点大小不够准确,仅供学习。

重排

在布局计算的第一步,就是判断是否需要重新布局。那么,有哪些情况会导致重新布局( 重排 )呢?

首先,网页被初次打开,一定会发生布局计算。

其次,JavaScript 代码通过 DOM、CSSOM 修改节点,致使元素的几何空间发生变化。

最常见的情况有如下几种:

  1. 添加或删除节点;
  2. 节点的位置发生变化( 浮动、定位 );
  3. 节点的大小发生变化( 内容、内外边距和边框宽度 )。

最后,scroll 事件、resize 事件、网页动画( 主要指 JavaScript 动画 )等会造成密集的重排,可能会出现网页卡顿现象。

减少重排的常用方法

重排是比较耗时的,而且,一旦布局发生变化,就会发生重排。我们在实际工作中,可以从以下两个方向,减少重排的发生:

批量修改 DOM

1、使用 display:none 隐藏节点,进行多次修改之后再显示。

例子:

<button>OK</button>

var showBox = (function() {
    var div = document.createElement("div");
    div.innerHTML = "hello";
    div.style.display = "none";
    document.body.appendChild(div);
    return div;
})();

document.querySelector("button").onclick = function() {
    showBox.style.display = "block";
}

2、使用文档片段( DocumentFragment )构建子树,构建完毕之后,添加进 DOM 模型。

例子:

<button>OK</button>

var showList = (function() {
    var ul = document.createElement("ul"),
        docF = document.createDocumentFragment();

    ["A", "B", "C"].forEach(function(v) {
        var li = document.createElement("li");
        li.innerHTML = v;
        docF.appendChild(li);
    });
    ul.appendChild(docF);
    return ul;
})();

document.querySelector("button").onclick = function() {
    document.body.appendChild(showList);
}

3、使用 innerHTML 和 outerHTML 批量替换 HTML 元素。

例子:

<button>OK</button>
<div></div>

var showList = (function() {
    return ul_HTML = "<ul><li>A</li><li>B</li><li>C</li></ul>";
})();

document.querySelector("button").onclick = function() {
    document.querySelector("div").innerHTML = showList;
}

*innerHTML 和 outerHTML 的实现原理,其实也是将字符串经过词法、语法分析之后,通过 DocumentFragment 构建子树,并最终添加进 DOM 模型。

批量修改样式

1、使用 className 批量修改节点的样式。

例子:

<style> 
    p {
        font-size: 20px;
        color: blueviolet;
        border: 1px solid #ccc;
    }
    
    .p_style {
        font-size: 30px;
        color: brown;
        border: 2px solid #ccc;
    }
</style>

<button>OK</button>
<p>hello</p>

<script>
    var change_style = function() {
        var p = document.querySelector("p");
        p.className = "p_style";
    };

    document.querySelector("button").onclick = function() {
        change_style();
    }
</script>

2、另外一个比较不常用的,就是使用 cssText 批量修改样式。

例子:

<style> 
    p {
        font-size: 20px;
        color: blueviolet;
        border: 1px solid #ccc;
    }
</style>

<button>OK</button>
<p>hello</p>

<script>
    var change_style = function() {
        var p = document.querySelector("p");
        p.style.cssText = "font-size: 30px;color: brown;";
    };

    document.querySelector("button").onclick = function() {
        change_style();
    }
</script>

Render 树

经过渲染引擎的处理,LayoutObject 对象保存了 DOM 节点以及其对应的布局信息,LayoutObject 对象已经知道了该如何绘制自己。然后,该对象会带着这些信息,插入到 Render 树上。

Render 树按照 DOM 树的排版规则进行排版,但两者并非一一对应的关系,只有 document 节点和可视节点才会出现在 Render 树中。

Render 树可以看作是渲染引擎为最后绘图,而构建的内部表示对象。但实际情况可能更为复杂。因为 HTML5 的出现,让绘图不再是简单地应对 HTML 元素,还有 2D、3D 图形,多媒体等等。

图片描述


如有错误,欢迎指正,本人不胜感激。

点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消