渲染器在创建完成并添加到渲染树时,并不包含位置和大小信息。计算这些值的过程称为布局(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
、offsetHeight
scrollTop
、scrollLeft
、scrollWidth
、scrollHeight
clientTop
、clientLeft
、clientWidth
、clientHeight
window.getComputedStyle()
currentStyle
documentFragment
、虚拟 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
样式属性。它会根据容器宽度的百分比计算得出一个绝对值。
然后加上水平方向的边框和补白。
如果渲染器在布局过程中需要换行,会立即暂停布局,并告知其父代需要换行。父代会创建额外的渲染器,并对其调用布局。