页面中的所有元素都被看作一个矩形盒子,这个盒子包含元素的内容(蓝色部分)、内边距(绿色部分)、边框(黄色部分)和外边距(橙色部分)(见下图)。
内边距(padding)是内容区周围的空间。给元素应用的背景会作用于元素内容和内边距。因此,内边距通常用于分隔内容,使其不致于散布到背景的边界。边框(border)会在内边距外侧增加一条框线,这条框线可以是实线、虚线或点划线。边框的外侧是外边距(margin),外边距是围绕在盒子可见部分之外的透明区域,用于在页面中控制元素之间的距离。
有一个与边框类似的属性,即轮廓线(outline)。这个属性可以在边框盒子外围画出一条线,但这条线不影响盒子的布局,也就是不会影响盒子的宽度和高度。因此,outline常用于调试复杂布局,或者演示布局效果。
默认情况下,元素盒子的width和height属性指的是内容盒子,也就是元素可渲染内容区的宽度和高度。这时候添加边框和内边距并不会影响内容盒子的大小,但会导致整个元素盒子变大。
通过修改box-sizing属性可以改变计算盒子大小的方式。box-sizing的默认值为content-box(内容盒子),width和height属性的值将会给到内容区。
如果把box-sizing的值修改为border-box(边框盒子),那么width和height属性的值将会包含内边距、边框和内容区。
内边距、边框和外边距可以应用于元素的四边,也可以应用于具体某一边。外边距甚至还可以使用负值,使得元素可以在页面中移动。
内边距和外边距的值可以是CSS规范中规定的任意长度单位(px、em或百分比)。其中,使用百分比长度时,有几个问题要注意。下面代码中的5%到底是什么意思?.block{ margin-left:5%; }
<div class="group"> <article class="block">article> div>
这里5%指的是父元素.group宽度的5%。如果.group宽度是100像素,那么这里左外边距的宽度就是5像素。
如果给一个元素的顶部和底部应用内、外边距,那么使用百分比值应该基于元素的高度来计算,对吧?错。因为元素的高度常常不会被声明,而且会因内容多少而差异很大,所以CSS规定,上、下方位的内、外边距,仍然基于包含块(containing block)的宽度来计算。这里,包含块就是其父元素,但有时候则不一定。具体我们会在本章后面再详细说明。
有时候,特别是在响应式布局中,给一个元素应用min-width和max-width值很有用。因为这样一来,块级盒子就可以默认自动填充父元素的宽度,但不会收缩到比min-width指定的值更窄,或者扩展到比max-width指定的值更宽。
与此类似的是min-height和max-height属性。不过在CSS中,设置任何高度值的时候都应该慎重。这是因为元素的高度通常应该取决于所包含的内容,不需要我们明确设定。否则,万一内容增多,或者文本字号变大,内容就可能跑到高度固定的盒子之外去。即使出于种种原因,需要明确设定默认高度,也最好使用min-height,因为这个属性允许盒子随内容扩展。
有了对盒模型的理解,下一步就可以探讨可见格式化及定位模型了。
大家常说p、h1和article这些元素都是块级元素。意思就是说,它们作为元素,显示为内容块或块级盒子(block box)的形式。相对而言,strong、span和time被称为行内元素,因为它们的内容会以行内盒子(inline box)的形式显示在行内。
块级盒子会沿垂直方向堆叠,盒子在垂直方向上的间距由它们的上、下外边距决定。
行内盒子是沿文本流水平排列的,也会随文本换行而换行。它们之间的水平间距可以通过水平方向的内边距、边框和外边距来调节。但行内盒子的高度不受其垂直方向上的内边距、边框和外边距的影响。此外,给行内盒子明确设置高度和宽度也不会起作用。
可以使用display属性改变生成的盒子类型。换句话说,可以通过把display属性设置为block,让span变得跟块级元素一样。如果把display属性设置为none,还可以让浏览器不为相应的元素生成盒子。如果不生成盒子,那么元素及其包含的内容就不会显示出来,也不会占用文档中的空间。当然,也可以把元素的display属性设置为inline-block。这样设置之后,元素就会像一个行内盒子一样水平排列。但这个盒子的内部仍然像块级元素一样,能够设置宽度、高度、垂直外边距和内边距。
HTML元素可以嵌套,元素盒子当然也可以嵌套。多数盒子都是基于明确定义的元素生成的。不过有一种情况,就算不明确定义元素也会生成块级盒子。比如,像下面这样,在section这个块级元素的开头加入“some text”。此时,“some text”就算没有定义为块级元素,也会被当成块级元素。
<section> some text <p>Some more textp> section>
这种情况下,这个盒子被称为匿名块盒子(anonymous block box),因为这个盒子并不与任何特定的元素相关。
类似的情况也存在于块级元素内部的文本级行盒子。假设有一个段落中包含三行文本,这三行文本的每一行都构成了一个匿名行盒子(anonymous line box)。除了使用:first-line伪元素来添加有限的排版和颜色相关的样式之外,不能直接给匿名块盒子或匿名行盒子应用样式。关键要知道,你在屏幕上看到一切,都会从属于某个盒子。
常规块盒子有一种机制叫作外边距折叠。外边距折叠的概念很简单,但实践中常常给人们布局网页带来困惑。简而言之,垂直方向上的两个外边距相遇时,会折叠成一个外边距。折叠后外边距的高度等于两者中较大的那一个高度。
- 当两个元素垂直堆叠时,上方元素的下外边距会与下方元素的上外边距相折叠。
- 在一个元素嵌套着另一个元素的情况下,假设没有内边距或边框来分隔外边距,它们的上、下外边距也会折叠。
- 甚至同一个元素的外边距都能折叠。假设有一个空元素,只有外边距而没有边框或内边距。此时,上外边距与下外边距接触,结果也会折叠。
外边距折叠好像很奇怪,实际上却很有用。想象一下一个拥有多个li的ul,如果相邻li之间的间距都是上、下外边距之和,结果就是li之间的间距等于顶部li与ul间距的两倍;有了外边距折叠,所有间距才会相等。
知道什么决定一个元素的包含块(一般情况指父元素)非常重要,正如前面盒子大小中所说,将内边距和外边距的值设置为百分比,包含块就是这些百分比值的计算依据。
确定元素的包含块,要看元素是如何定位的。如果元素的定位方式为静态定位(即不指定position属性的值)或相对定位,则其包含块的边界就计算到一个最近的父元素,该元素的display属性值必须能够提供类似块级的上下文,如b1lock、inline-block、table-cell、list-item等。
默认情况下,width、height、margin和padding的值为百分比时,就以该父元素的尺寸为计算依据。如果当前元素的定位模型改成了absolute或fixed,那么计算依据就会发生变化。接下来,我们就逐个讨论不同的定位模型,以及如何确定与之对应的包含块。
把一个元素的position属性设置为relative,该元素仍然会呆在原来的地方。但此后,可以通过设置top、right、bottom和left属性,使该元素相对于初始位置平移一定距离。比如设置top属性为20像素,该元素就会相对于其初始位置垂直向下平移20像素。而设置left属性为20像素,则会将该元素向右移动20像素,其左侧会出现空白。
无论是否位移,相对定位的元素仍然会在文档流中占用初始的空间。因此,这样平移元素会导致它遮挡其他元素。
相对定位事实上是常规文档流定位模型的一部分,因为元素还是相对于它在常规流中的初始位置来定位。绝对定位则会把元素拿出文档流,因此也就不会再占用原来的空间。与此同时,文档流中的其他元素会各自重新定位,仿佛绝对定位的那个元素没有存在过一样。
绝对定位元素的包含块是距离它最近的定位祖先,也就是position属性设置为static之外任意值的祖先元素。如果没有这么一个定位祖先,那么它就相对于文档的根元素即html元素定位。文档的根元素也叫作起始包含块(initial containing block)。
与相对定位的盒子类似,绝对定位的盒子也可以相对于其包含块向上、下、左、右方向平移。平移绝对定位的元素为我们提供了极大的灵活性,因为可以把元素移动到页面的任意位置。绝对定位的盒子是脱离了常规文档流的,因此可能会遮挡页面上的其他元素。为了控制这些盒子层叠的次序,可以设置一个叫z-index的属性。z-index属性数值越大,盒子在层叠中的次序就越靠近用户的眼睛。
将position属性设置为fixed就可以将元素设置为固定定位,固定定位是由绝对定位衍生出来的,不同之处在于,固定定位元素的包含块是视口(viewport)。因此,固定定位可用来创建始终停留在窗口相同位置的浮动元素。很多网站都使用这个技术让导航区始终保持可见,有的固定侧栏,有的固定顶栏。这样能确保网站的可用性,因为用户不必再费事寻找了。
另一种可见格式化模型是浮动模型。给元素添加float属性,可以使盒子向左或向右移动,直到其外边沿接触包含块的外边沿,或接触另一个浮动盒子的外边沿。浮动盒子也会脱离常规文档流,因此常规流中的其他块级盒子的表现,几乎当浮动盒子根本不存在一样。稍后我们再解释为什么说“几乎”。
如下图所示,向右浮动Box1时,Box1会脱离文档流并向右移动,直至其右边沿接触包含块的右边沿。同时,Box1的宽度也会收缩为适应于其中内容的最小宽度,除非通过width或min-width/max-width明确设置其宽度。
如下图所示,向左浮动Box1时,它脱离文档流并向左移动,直至其左边沿接触包含块的左边沿。Box1已经不在文档流中,因此不会再占用空间,这导致它浮于上方,遮住了Box2。如果向左浮动全部3个元素,Box1会向左移动,直到接触其包含块;另外两个盒子也向左移动,直到接触自己前面的浮动盒子。
如果包含元素太窄,无法容纳所有浮动元素水平排列,则后面的浮动元素会向下移动。如果浮动元素高度不同,则后面的浮动元素在向下移动时可能会“卡”在前面的浮动元素右侧。
通过前面的学习我们了解到,浮动元素会脱离文档流,因此不会再像非浮动元素一样影响其他元素。实际上,严格来讲并非如此。如果浮动元素后面跟着的是常规文档流中的元素,那么这个元素的盒子就会当浮动元素不存在一样,该怎么布局就怎么布局。但是,这个元素盒子中的文本内容则会记住浮动元素的大小,并在排布时避开它,为其留出相应的空间。从技术角度来讲,就是跟在浮动元素后面的行盒子会缩短,从而为浮动元素留空,造成文本环绕浮动盒子的效果,事实上,浮动就是为了在网页中实现文本环绕图片的效果而引入的一种布局模型。
要阻止行盒子环绕在浮动盒子外面,需要给包含行盒子的元素应用clear属性。clear属性的值有left、right、both和none,用于指定盒子的哪一侧不应该紧挨着浮动盒子。很多人认为clear属性只是简单地删除几个用于抵消前面浮动元素的标记,事实却没有这么简单。清除一个元素时,浏览器会在这个元素上方添加足够大的外边距,从而将元素的上边沿垂直向下推移到浮动元素下方。因此,如果你给“已清除的”元素添加外边距,那么除非你的值超过浏览器自动添加的值,否则不会看到什么效果。
其实想要清除浮动还可以通过::after伪元素实现:
包含浮动元素的盒子::after{ content: " "; display: block; clear: both; }
当元素在页面上水平或垂直排布时,它们之间如何相互影响,CSS有几套不同的规则,其中一套规则叫作格式化上下文(formatting context)。前面我们已经介绍了行内格式化上下文(inlineformatting context)的一些规则。比如,垂直外边距对于行内盒子没有影响。类似地,有的规则适用于块级盒子的叠放,比如上面介绍的外边距折叠。
此外,有些规则规定了页面必须自动包含突出的浮动元素(否则浮动元素中的内容可能会跑到可滚动区域之外),而且所有块级盒子的左边界默认与包含块的左边界对齐(如果文字顺序是从右向左,那么与包含块的右边界对齐)。这组规则就是块级格式化上下文(block formatting context,BFC)。还有些规则允许元素建立自己内部的块级格式化上下文,包括:
- display属性值设置为inline-block或table-cell之类的元素;
- float属性值不是none的元素;
- 绝对定位的元素;
- overflow属性值不是visible的元素。
前面说过,块边界接触其包含块边界的规则同样适用于前面是浮动元素的内容。浮动元素从页面流中移出后,通过触发其后的元素中行盒子的缩短行为,制造了为自身腾出四周空间的视觉效果。而其后的元素仍然会按照需要,在浮动元素下方拉伸。
当一个元素具备了触发新块级格式化上下文的条件,并且挨着一个浮动元素时,它就会忽略自己的边界必须接触自已的包含块边界的规则。此时,这个元素会收缩到适当大小;不仅行盒子如此,所有盒子都如此。
CSS的Intrinsic and Extrinsic Sizing Level3模块定义了一组可以应用给(min-和max-)width和height属性的关键字,而非像素或百分比这种长度值。这些关键字代表了明确的长度,要么继承自周围的上下文(外在大小),要么源于元素自身的内容(内在大小),具体数值由浏览器决定。这样可以代替以往使用的隐含值,比如把某个属性设置为auto,或者使用浮动或块级格式化上下文,在不设置width的情况下达到收缩适应的目的。
本节不会讨论所有关键字,只想讲一讲与前面示例相关的contain-floats。使用这个关键字,可以仅通过以下代码就让元素包含浮动,这样就省了很多事:.myThing { min-height: contain-floats; }
目前,支持这个模块中定义的关键字的浏览器还很少。但无论如何,我们相信这种更稳健的指定尺寸的方式在未来一定非常有用,毕竟可以省掉同时使用多种技术的麻烦嘛。
对于CSS这种视觉表现语言来说,稳健又灵活的布局模型无论如何都是必需的。虽然道理显而易见,但这种模型的诞生却并不容易。过去,我们曾想方设法地利用这门语言中可用的特性来达成自己的目标,哪怕那些特性并不好用。比如最早我们曾使用表格布局,但问题是代码臃肿、语义不当。近来又在使用浮动和绝对定位,但这些技术同样也并非为页面布局而设计。无论是表格还是浮动与定位,都有非常严重的局限性,使用它们只是不得已的选择。
可喜的是,出现了一些专门针对创建灵活、稳健页面布局的CSS模块。后面会详细介绍其中几个模块,此处先概览一下它们的主要功能。
弹性盒布局模块(Flexible Box Layout Module),常被称为Flexbox,是CSS3新引入的一种布局模型。Flexbox支持对子元素水平或垂直布局,以及设置这些子元素的大小、间距和对齐方式。此外,Flexbox还支持改变元素渲染到页面上的次序,可以跟它们在HTML中的次序不同。作为CSS常规流模型(行内和块)的升级版,无论是调整内容本身还是适应内容大小,Flexbox都做到了既精确又灵活。
Flexbox已经得到浏览器的广泛支持,只是在旧版本IE中缺乏支持或支持不完整。好在可以将Flexbox与浮动等其他技术组合使用,以确保跨浏览器布局的稳健。
网格布局(grid layout)是CSS中最早成熟的高层布局工具,目标是取代浮动和定位元素的布局方式。网格布局实现了源代码次序的完全分离,从内容结构和个别模块的表现中抽象出了网格系统。Flexbox关注“微观”,而网格系统关注“宏观”,二者正好互补。
多栏布局模块(Multi-column Layout Module)的用意很明确,就是实现内容的多栏布局。比如,要排成像报纸那样的多栏样式。可以先指定栏数,也可以先指定每一栏的宽度,然后让浏览器根据可用宽度自动确定栏数。当然,还可以控制栏间距,并在其中应用类似边框的视觉效果。
CSS Regions Module Level1可以实现内容在不同元素间的灌文接排。可以把一个元素作为内容来源,但它不在常规文档流中,其内容可以灌排到页面中的其他占位元素。这意味着布局不再受HTML中元素次序的影响,也就是把布局表现从内容结构中解耦了出来。
使用CSS Region排出的版式,在以前是无法只用CSS实现的,它为将来在网页中再现印刷品的排版样式奠定了基础。不过,很少有浏览器厂商有兴趣实现CSS Region,因此我们可能在相当长的一段时间内都无法实际使用它。