布局是一个递归过程。它起始于根渲染器,对应于HTML文档元素。布局递归的处理完某些或全部框架层次,计算每个渲染者需要的位置信息。
根渲染者的位置是0,0,它的面积大小是视口—浏览器窗口的可见部分。
所有渲染者有一个“layout”或“reflow”方法,每个渲染者调用需要布局的子元素的layout方法。
脏字节系统
为了每次小改动不需要做一次完整布局,浏览器使用一个“脏字节”系统。改动过或增加过东西的渲染者把它自己和子元素标注为“脏”的—需要进行布局。
有两个标识—“脏的”和“子元素是脏的”。子元素是脏的意味着尽管渲染者本身没问题,它却有至少一个子元素需要进行布局。
全局和增量布局
布局可能在整个渲染树上触发—这是“全局”布局。这可能由下述事件造成:
1 影响到所有渲染者的全局样式的改变,象字体大小改变。
2 由屏幕缩放造成。
布局可以是增量的,只有脏的渲染者才会被布置上去(需要额外布局时可能会导致一些损害)。
当渲染者标注为“脏”的时增量布局触发(异步的)。例如来自网络的另外的内容被添加到DOM树后,新的渲染者要添加到渲染树上时。
图20:增量布局—只有脏的渲染者和它们的子元素被布局。
异步和同步布局
增量布局会异步的完成。Firefox把增量布局的“重排命令”排入队列,一个任务触发器用来批量执行这些命令。Webkit也有一个定时器执行增量布局—遍历树并且布局那些“脏”渲染者。
脚本请求样式信息,象“offsightHeight”可能会触发同步的增量布局。
全局布局通常会同步触发。
由于某些属性,有时布局会在初始布局之后以回调的形式触发,象滚动位置变化。
优化
当一次布局由“resize”或者渲染者位置(不是尺寸)的改动触发时,渲染者的尺寸从缓存中取得,不会重新计算。
在某些情况下—只有一棵子树修改时,布局不会从根开始。这在改变只发生在区域,不影响到周围的情况下才可能发生—象文本插入了文本域(否则每次键入都会触发从根开始的布局)。
布局过程
布局通常有下列模式:
1 父渲染者决定它自己的宽度
2 父渲染者检查子元素并且:
a 放置子渲染者(设置它的x和y座标)。
b 如果需要的话(它们是“脏”的,或者我们在全局布局模式,或其它一些原因)调用子布局—这会计算出子元素的高度。
3 父渲染者使用子渲染者叠加的高度,和margin及padding的高度来设置它自己的高度—这个值会被父渲染者的父元素使用。
4 设置“脏”标识为假。
Firefox使用一个“state”对象(nsHTMLReflowState)作为布局(以“reflow”命名)的参数。在其它参数中,state包括父元素的宽度。
Firefox的布局的输出是一个“metrics”对象(nsHTMLReflowMetrics)。它里面包含有渲染者计算出的高度。
宽度计算
渲染者的宽度用容器块的width,渲染者样式的“width”属性,margin和border属性等来计算。
例如以下div的宽度:
<div style="width:30%"/>
将会被Webkit象下面这样计算(使用类RenderBox的calcWidth方法):
1 容器宽度是窗口的availableWidth和0之间的最大值。availableWidth在这种情形下是用下列公式计算出的contentWidth:
clientWidth() - paddingLeft() - paddingRight()
clientWidth和clientHeight表示对象内部排除了边框和滚动条后的值。
2 元素宽度是“width”样式属性。它将通过把容器宽度的百分比计算成绝对值来完成。
3 再加上水平border和padding的(宽度)值。
目前计算出了“建议宽度”,现在来计算最小和最大宽度。
如果建议宽度比最大宽度更高,那么会使用最大宽度。如果比最小宽度(最小的不可分割单位)更小,那么使用最小宽度。
以上数值会缓存起来,供宽度未改变而需要布局时使用。
换行
当一个布局中间的渲染者认为需要换行时,它停下来依次通知它的父元素自己需要换行了。父元素会创建另外的渲染者,并对它们调用布局方法。
(待续)
原创文章,作者:苏葳,如需转载,请注明出处:https://www.swmemo.com/2090.html