渲染器在创建完成并添加到渲染树时,并不包含位置和大小信息。计算这些值的过程称为布局(Layout) 或 重排(Reflow)。
HTML 采用 基于流的布局模型,这意味着大多数情况下只要一次遍历就能计算出几何信息。处于流中靠后位置元素通常不会影响靠前位置元素的几何特征,因此布局可以按从左至右、从上至下的顺序遍历文档。
(0, 0),其尺寸为视口(也就是浏览器窗口的可视区域)。布局是一个 递归 的过程。它从根渲染器(对应于 HTML 文档的 <html> 元素)开始,然后递归遍历部分或所有的渲染器层次结构,每一个渲染器都会通过调用其需要进行布局的子代的 layout 方法,为每一个需要计算的渲染器计算几何信息。任何有可能改变元素位置或大小的样式都会触发这个 Layout 事件。
为避免对所有细小更改都进行整体布局,浏览器采用了一种 Dirty 位系统。如果某个渲染器发生了更改,或者将自身及其子代标注为 dirty,则需要进行布局。类似于脏检测。
有 dirty 和 children are dirty 两种标记方法。children are dirty 表示尽管渲染器自身没有变化,但它至少有一个子代需要布局。
dirty 渲染器进行布局(这样可能存在需要进行额外布局的弊端)全局布局往往是同步触发的。 有时,当初始布局完成之后,如果一些属性(如滚动位置)发生变化,布局就会作为回调而触发。
增量布局是异步执行的。
reflow 命令加入队列,而调度程序会触发这些命令的批量执行dirty 渲染器进行布局。 请求样式信息(例如 offsetHeight)的脚本可同步触发增量布局。如果布局是由 大小调整 或 渲染器的位置(而非大小) 改变而触发的,那么可以从缓存中获取渲染器的大小,而无需重新计算。在某些情况下,只有一个子树进行了修改,因此无需从根节点开始布局。这适用于在本地进行更改而不影响周围元素的情况,例如在文本字段中插入文本(否则每次键盘输入都将触发从根节点开始的布局)。
因为这个优化方案,所以你每改一次样式,它就不会回流(Reflow)或重绘(Repaint)一次。但是有些情况,如果我们的程序需要某些特殊的值,那么浏览器需要返回最新的值,而会有一些样式的改变,从而造成频繁的回流和重绘。比如获取下面这些值,浏览器会马上进行回流:
offsetTop、offsetLeft、offsetWidth、offsetHeightscrollTop、scrollLeft、scrollWidth、scrollHeightclientTop、clientLeft、clientWidth、clientHeightwindow.getComputedStyle()currentStyledocumentFragment、虚拟 DOM、改为 display:none 再显示fixed 或 absoult 的 position,脱离文档流布局通常具有以下模式:
dirty 的,或者这是全局布局,或者出于其他某些原因),这会计算子渲染器的高度false渲染器宽度是根据容器块的宽度、渲染器样式中的 width 属性以及边距和边框计算得出的。
例如以下 div 的宽度:
<div style="width: 30%"></div>
将由 Webkit 计算如下(BenderBox 类,calcWidth 方法):
容器的宽度取容器的 availableWidth 和 0 中的较大值。availableWidth 在本例中相当于 contentWidth,计算公式如下:
clientWidth() - paddingLeft() - paddingRight();
clientWidth 和 clientHeight 表示一个对象的内部(除去边框和滚动条)。
元素的宽度是 width 样式属性。它会根据容器宽度的百分比计算得出一个绝对值。
然后加上水平方向的边框和补白。
如果渲染器在布局过程中需要换行,会立即暂停布局,并告知其父代需要换行。父代会创建额外的渲染器,并对其调用布局。