布局的问题

对于浏览器来说,CSS布局是这样一个问题:给定一颗DOM树,如果将他们在屏幕上列举出来。在这过程中,浏览器面临着若干限制。首先,屏幕的大小是有限的,也就是意味着浏览器的给DOM文档使用的可视区域(简称视界)是有限的。其次,视界是共享资源,在上面操作还面临排布次序的问题,也就是说浏览器必须决定出一种次序,来最终同步其在视界上的元素排布,最终达到序列化的效果。

屏幕大小的限制,是任何图形化程序都会面临的问题。一般的解决方法是裁剪和层叠。浏览器可以在一个抽象的画布上布局元素,最后在显示的时候将超出视界大小的部分去除,这就是裁剪。为了能够让画布的不同部位被观看到,还可以使用滚动条将画布的不同区域映射到可视区域。除了裁剪之外,另一种方式是层叠,相当于使用多层画布来布局,然后决定哪层画布的元素在那层之前显示。

对于布局序列化的问题,浏览器必须遍历DOM树,然后把元素一个一个拎起来,然后决定它的排布位置。CSS Level 2中描述了一个Normal Flow(又称 Flow Layout),说明一般情况下,浏览器应该如何排布元素。总的来说,排布的过程是先行后列,根据阅读习惯(从左到右,还是从右到左),决定起点坐标。

对于中文以及拉丁语系的文字系统,排布次序是从左到右,从上到下。这样可以形成一个坐标系,用来描述画布。坐标系原点在左上角,x轴从左向右,y轴从上到下。排布过程可以先沿着x轴排布,然后沿y轴排布。除此之外,还可以有一个z轴,用来描述画布的层叠。

元素的排布

DOM元素是一种嵌套的树形结构,在排布的时候,是被当成一个方格。一个方格可以套多个以及多层子方格,从而能够跟DOM树的树形结构相匹配。排布的时候,要解决的问题是如果决定方格的走位和大小。

对于方格的走位,可以使用它的display属性来控制,常见的有两种平铺(inline)和竖排(block)两种。平铺是指沿x轴方向铺设元素,直到遇到一个折返点,然后从原点开始重新铺设。而竖排则是指沿y轴方向排列元素,不折返,但是每个元素之间上下分隔。

平铺元素的话,还可以分栏,即使用column-count和column-gap来划定分栏,也就是设定多个折返点。

当一个平铺元素遇到一个竖排元素时,要主动折返,形成竖排元素要和平铺元素上下隔开的局面。当一个平铺元素包含一个竖排元素(非空)时,这个平铺元素会失去平铺性,变成一个竖排元素。所以,平铺元素可以看成是竖排元素的一种特殊形式,在需要的时候可以转化为竖排。但是竖排元素可以转为平铺吗?也可以,但是需要指定display: inline-block

Normal Flow(定式流向)其实模仿的是写字时候的流向。

除了定式流向以外,在局部还可以采用flex和grid两种布局。

在没有display: inline-block的时代,需要使用float来模拟。

在浏览器严重,元素是一个方格,由外围的边框(border)以及里面的内容构成。边框和内容之间是可以指定填充区域,边框之外可以指定和其他元素的缓冲区域。我们可以用内关来表示内容区域的边界,外关来表示边框所包围区域的大小。元素的wide和height属性可以指定元素的大小。但是历史以来,存在一种起义,wide和height所指的是内关还是外关。为了澄清,可以使用box-sizing属性来区分,如果是content-box则只计算内关,如果是border-box则计算外关。

缓冲区、边框、填充区以及内容区,构成了方格的箱体模型,也就是构成了元素布局的基础。下面是一个居中对齐一个竖排元素的例子:

<style>
#main {
  width: 600px;
  margin: 0 auto; 
}
</style>

<div id="main"></div>

上面的div能够居中对齐,因为它是竖排,所以独占一行。然后它的左右缓冲区是自动计算的,所以默认情况下就是左右平均分。

上面的width可以替换成max-width,在视界变小的时候提供更好的适应性。

布局设计

元素的位移

常态排布中,元素所处的位置是固定的,也就是说其位移是静态的,用position: static表示。位移可以设成其他多种值:

  • 设为relative表示元素的位移是锚定在常态排布的固定位置,通过top, left, right, bottom等属性设置位移量
  • 设为fixed,则表示元素位移的锚定点不是常态排布的固定位置(因此也不影响常态排布)而是视界坐标系原点
  • 设为absolute,则表示元素位移的锚定点不是常态排布的固定位置(因此也不影响常态排布),而是上一个脱离常态排布的元素(如果没有的话则者是body)。absolute可以和fixed以及relative搭配,玩出更多花样。

采用元素位移来布局的例子,比如一个两栏布局,可以先变后静。左边是导航,右边是内容,可以先设计好内容的位置。然后通过设置内容的左缓冲区来给导航栏留出位置。然后通过搭配relative和absolute来将导航栏固定到内容的左缓冲区中。这就是说基于缓冲区的布局。

元素的浮动

浮动(float)元素原来的目的是为了在平铺元素周围嵌入图片。浮动元素就像是从平铺元素所占区域中抠出来的一样。当平铺元素遇到浮动元素时会绕着走。可是对于竖排元素呢,不会考虑浮动元素,会和它重合。为了让竖排元素能够躲避浮动元素,需要使用clear属性来指定在哪个方向躲避浮动。

如果一个竖排元素中包含一个浮动元素,因为排布竖排元素的的时候不考虑浮动元素,所以浮动元素可能会超出竖排元素的大小(这是极少数情况,元素内容超出元素大小)。这时候需要在竖排元素指定overflow: auto来让竖排元素考虑浮动元素的大小。

由于排布竖排元素的时候默认不考虑浮动元素,所以就可以让浮动元素存在竖排元素的缓冲区之中,形成一定的布局。就像上面的两栏布局的例子,可以把导航栏做成浮动元素。

也可以使用inline-block来实现跟float等价的布局效果。但是inline-block受vertical-align影响,需要把vertical-align设为top。同时记住inline-block之间是平铺关系。

其他

(完)

2019-10-17更新

The Magic of CSS: Layout给了我一个提示,告诉我竖排(block)、平铺(inline),以及横排(inline-block)的区别:

  • 竖排:元素的width从父元素继承;height由内容决定;width和height设置对元素起作用
  • 平铺:元素的width和height完全由内容决定;width和height设置对元素不起作用
  • 横排:和平铺一样,width和height由内容决定;width和height设置对元素起作用

浏览器在布局的时候,不仅需要从上到下考虑预先分配的长宽,还要从下往上倾听具体内容(比如文字)的大小。

(更新)