w3ctech

CSS 视觉格式化基础 - CSS 布局基础

Eric Meyer 的 CSS 权威指南第四版拖了很多年还没有出版的迹象,本文是翻译第四版中一章(Eric Meyer 把新版的每一章单独成一本书,本文就是翻译《Basic Visual Formatting in CSS》一书)。

内容:

  • 学习元素盒类型详情,包括 block、inline、inline-block、list-item、run-in 盒
  • 改变元素生成的盒子类型,从 block 到 inline,或者从 list-item 到 inline
  • 深入水平和垂直块盒格式化的复杂性
  • 探索行内布局的核心概念:匿名文本、em 盒、内容区、leading、行内盒 和行盒
  • 理解可替换和非可替换行内元素的格式化区别。

概述: 本书主要讲解 CSS 可视化渲染的理论部分。为什么用整本书来写可视化渲染的理论基础呢?答案是,作为一个 CSS 内包含的如此开放和强大的模型,在一本书中想要涵盖属性和效果的每一种可能的组合方式是不可能的。很明显,我们会在使用 CSS 时发现更多的新方式。在探索 CSS 的课程中,你会遇到浏览器中看似奇怪的行为。在深入理解可视化渲染模型如何在 CSS 中起作用后,你才能判断一种行为是否是 CSS 定义的渲染引擎的正确结果(如果意外),或者是否是无意中发现的一个需要报告的 bug。

一、盒子基础

CSS 假定每个元素生成一个或者多个矩形盒,这称为元素盒(规范将来的版本可能会允许非矩形盒,并且实际上已经有三个提案,但是现在所有还是矩形)。每个元素盒的中心有一个内容区。这个内容区周围有可选的 padding(内边距)、border(边框)、outline(轮廓) 和 margin(外边距)。这些区域之所以被认为是可选的,是因为它们的宽度都可以被设置为零,实际上就是将它们从元素盒中删除了。图 1 是一个示例内容区,内容区的周围还有内边距、边框和外边距。

-c

图 1. 内容区及其周边

每个外边距、边框和内边距可以用各种指定边属性设置,比如 margin-leftborder-bottom,以及缩写属性设置,比如 padding。如果有轮廓的话,它是没有指定边属性的。内容的背景(例如颜色或者平铺图像)默认是应用在内边距以内。外边距总是透明的,从而可以看到父元素的背景。内边距的长度不能为负,但是外边距可以。在后面我们会研究负外边距的效果。

边框由已定义的样式(例如 solidinset)生成,其颜色用 border-color 属性设置。如果没有设置颜色,那么边框颜色就会采用元素内容的前景色。例如,如果段落的文本是白色,那么除非作者显式声明一个不同的边框颜色,否则围绕该段落的任何边框都是白色。如果边框的样式是有缝隙的类型,那么默认可以透过这些缝隙看到元素的背景。最后,边框的宽度不能是负数。

元素盒的各个部分可能会被很多属性所影响,比如 widthborder-right。本书将会用到很多属性,即使在这里没有定义。

快速复习

下面我们快速复习一下将要讨论的盒子类型,以及一些重要的术语:

  • 常规流(Normal flow):在西方语言中,常规流是从左到右、从上到下渲染文本,这也是传统 HTML 文档的熟悉文本布局。要注意的是,在非西方语言中,流的方向可能会改变。大多数元素都在常规流中,要让元素脱离常规流,唯一的方式是让元素成为浮动、定位元素,或者让它进入弹性盒或者网格布局元素。记住,本章中只讨论常规流中的元素。

  • 非可替换元素(Nonreplaced element):非可替换元素是元素的内容被包含在文档内的元素。例如,段落 p 就是一个非可替换元素,因为它的文本内容是在元素本身内可以找到。

  • 可替换元素(Replaced element):可替换元素是充当某种占位符的元素。可替换元素的典型示例是 img 元素,该元素只指向一个图像文件,该文件会被插入到文档流中 img 元素所在的地方。大多数表单元素也是可替换元素(例如,<input type="radio">)。

  • 根元素:根元素是文档树顶部的元素。在 HTML 文档中,根元素就是 html。在 XML 文档中,根元素可以是语言所允许的任何任务;例如,RSS 文件的根元素是 rss

  • 块盒(Block box):块盒是段落、正文标题或者 div 等元素生成的盒子。这些盒子在常规流中时,在盒子的前后产生换行,这样常规流中的块盒就一个挨着一个垂直堆叠。任何元素都可以通过声明 display: block 生成块盒。

  • 行内盒(Inline box):行内盒是 strong 或者 span 这些元素生成的盒子。这些盒子不会在其前后产生换行。任何元素都可以通过声明为 display: inline 生成行内盒。

  • 行内块盒(Inline-block box):行内块盒是内部像块盒,但是外部像行内盒的盒子。它的作用与可替换元素相似,但是不完全相同。想像一下把一个 div 像一个行内图像一样插入一行文本中,你就会明白。

还有几个其它类型的盒子,例如 table-cell 盒,但是它们因为各种原因不会在本书中讲解 - 最主要的是它们的复杂性需要专门一本书来讲解,并且极少有开发者会实际要经常对付它们。

包含块

还有一种盒子我们需要用以小节专门来详细讲解,就是包含块(The Containing Block)。

每个元素的盒子都是相对于它的包含块来布局,换句话说,包含块是一个盒子的布局上下文。CSS 定义了判断盒子的包含块的一系列规则。为了保持专注,这里我们只讲解那些与本书所讲解的概念有关的规则。

对于一个在常规、西方语言文本流中的元素,包含块由生成列表条目或者块盒的最近的祖先的内容边缘(content edge)组成,包括所有表格相关的盒子(例如,表格单元生成的盒子)。考虑如下的标记:

<body>
    <div> 
        <p>This is a paragraph.</p>
    </div>
</body>

在这个很简单的标记中, p 元素的块盒的包含块是 div 元素的块盒,因为 div 元素的块盒是 p 元素的块盒的最近的祖先元素块盒或者列表条目盒(本例中是块盒)。同样,div 的包含块是 body 的盒子。因此,p 的布局依赖于 div 的布局,而 div 的布局又依赖于 body 元素的布局。

在此之上,body 元素的布局依赖于 html 元素的布局。html 元素生成的盒子称为初始包含块(initial containing block)。比较独特的是,这个初始包含块的大小是由视口,而不是根元素内容的大小来决定的。对于屏幕媒介来说,视口就是浏览器窗口,对于打印媒介来说,视口就是页面的可打印区域。这是一个很细微的区别,通常不是很重要,但是它确实存在。

二、改变元素的显示方式

通过给 display 属性设置一个值,可以影响用户代理显示的方式。现在我们已经仔细观察了视觉格式化,下面我们考虑一下 display 属性,用本书早前的概念讨论几个 display 属性值。

display 可选取值: none | inline | block | inline-block | list-item | run-in | table | inline-table | table-row-group | table-header-group | table- footer-group | table-row | table-column-group | table-column | table-cell | table-caption | inherit 初始值: inline 适用于: All elements 能够继承: No 计算值: Varies for foated, positioned, and root elements (see CSS2.1, section 9.7); otherwise, as specified

这里我们忽略表格相关的值,因为它们对于本文来说太复杂;我们还会忽略 list-item,因为它与块盒很相似。我们会花了相当多的时间讨论块盒和行内盒,然后要研究 inline-blockrun-in。不过在此之前,我们先花点时间谈谈如何通过改变元素的显示角色,来改变布局。

改变角色

格式化文档时,如果能改变一个元素生成的盒子的类型,显然是很方便的。例如,假设在 nav 元素中有一系列链接,我们要让它作为垂直侧边栏布局:

<nav>
    <a href="index.html">WidgetCo Home</a>
    <a href="products.html">Products</a>
    <a href="services.html">Services</a>
    <a href="fun.html">Widgety Fun!</a>
    <a href="support.html">Support</a>
    <a href="about.html" id="current">About Us</a>
    <a href="contact.html">Contact</a>
</nav>

我们可以把所有这些链接都放进表格单元中,或者把每个链接都包在自己的 nav 中,或者我们可以像这样,让这些链接都变成块级元素:

nav a {display: block;}

这段代码会让导航 nav 内的每个 a 元素都成为块级元素。如果再多添加点样式,可以得到如图 2 所示的结果:

-w138

图 2. 将显示角色从 inline 变为 block

如果我们想让不支持 CSS 的浏览器有行内元素的导航链接,但是让这些链接像块级元素一样布局,那么改变显示角色就很有用。链接变成块后,我们可以像对 divp 元素一样对它们添加样式,好处是整个元素盒都成了链接的一部分,这样如果鼠标指针停留在元素盒中的任何地方,用户都可以点击链接。

我们可能还想让元素变成行内元素。假如有如下的姓名无序列表:

<ul id="rollcall">
    <li>Bob C.</li>
    <li>Marcio G.</li>
    <li>Eric M.</li>
    <li>Kat M.</li>
    <li>Tristan N.</li>
    <li>Arun R.</li>
    <li>Doron R.</li>
    <li>Susie W.</li>
</ul>

对于以上标记,假如我们想姓名变成一连串行内的姓名,每个姓名之间有竖线(并且列表的两头也有竖线)。要这样做的唯一方法是改变它们的显示角色。如下的规则有图 3 所示的效果:

#rollcall li {
    display: inline; 
    border-right: 1px solid; 
    padding: 0 0.33em;
} 

#rollcall li:first-child {
    border-left: 1px solid;
}

-c-w401

图 3. 显示角色从 list-item 变为 inline

还有很多其它方法可以在设计中充分利用 display。要有创意,看看你能发明什么!

但是,必须指出的是,改变元素的显示角色,并不会改变其本质。换句话说,让一个段落生成一个行内盒,并不会让这个段落转变为行内元素。例如,在 XHTML 中,有些元素是块级元素,有些是行内元素。(还有些是 'flow' 元素,不过我们暂且忽略)。行内元素可以是块元素的后代,但是反之则不对。因此,span 可以放在 p 内,但是 span 内不能包含有 p。不管如何给元素添加样式,都是如此。考虑如下标记:

<span style="display: block;">
  <p style="display: inline;">this is wrong!</p>
</span>

这个标记是无效的,因为块元素(p)嵌入在行内元素(span)中。显示角色的改变与也不能改变这个事实。display 之所以得名,是因为它影响元素如何显示,而不是因为它改变了元素的类型。

说了这么多,下面我们详细研究不同类型的盒:块盒、行内盒、inline-block 盒、list-item 盒和 run-in 盒。

三、块盒

块盒的表现有时候可以预测,有时候又很让人吃惊。例如,盒子位置沿着水平和垂直轴的处理可能是不同的。为了彻底理解块盒是如何被处理的,你必须清晰地理解一些边界与区域。它们在图4 中被详细展示。 -c-w410

图 4. 完整的盒模型

默认情况下,块盒的宽度 width 是从左内边缘到右内边缘的距离,高度 height 是内顶到内底的距离。这两个属性都可以被应用到生成块盒的元素上。还有一种情况是,我们可以用 box-sizing 属性改变这些属性被看待的方式。

box-sizing 取值:content-box | padding-box | border-box | inherit 初始值:content-box 适用于:所有接受 width 或 height 值的元素 能否继承:no 计算值:as specified

该属性是可以影响 widthheight 值实际能做的方式。如果你声明 width: 400px,但是没有为 box-sizing 声明一个值,那么元素的内容盒将是 400px 宽;内边距、边框等都会被添加上去。另一方面,如果声明了 box-sizing: border-box,那么这 400px 将是从左外边框边缘到右外边框边缘;边框或者内边距都会被放在这个距离内,所以就压缩了内容区的宽度。如图 5 所示。

-w379

图 5. box-sizing 的效果

我们正在这里讨论 box-sizing 属性,是因为,如上所述,它适用于 "可以接受 width 或者 height 值的所有元素"。最常见的是生成块盒的元素,当然它还包括可替换的行内元素(比如图像)以及 inline-block 盒。

不同的 width、height、内边距和外边距相结合,共同决定文档的布局。大多数情况下,文档的高度和宽度是由浏览器基于可用的显示区域以及其它因素自动决定的。当然,有了 CSS,我们可以声称更直接控制元素的大小和显示的方式。

一)水平格式化

水平格式化通常比我们想像的更复杂。部分的复杂性在于必须处理 box-sizing 的默认行为。对于 content-box 默认值,width 的给定值影响的是内容区的宽度,而不是整个可见元素盒。考虑如下的示例:

<p style="width: 200px;">wideness?</p>

这行代码会让段落的内容区宽度变为 200px。如果给该元素指定一个背景,就会看得很清楚。但是,如果还指定了内边距、边框或者外边距,这些都会添加到宽度值上。假如我们这样做:

<p style="width: 200px; padding: 10px; margin: 20px;">wideness?</p>

可见的元素盒现在是 220px 宽,因为内容的右边和左边分别增加了 10px 的内边距。而外边距则会在左右两边再延伸 20px,整个元素盒的宽度就达到 260px。如图 6 所示。

-c-w162

图 6 加上内边距和外边距

当然,如果我们修改样式,让 box-sizingborder-box,那么结果又会不同。此时,可见的盒子将是 200px 宽,内容为 180px,两边的外边距共 40px,整个盒子的宽度是 240px。如图 7 所示。

-c-w144

图7 减去内边距

无论哪种情况,这里有一个简单的规则:常规流中块盒的水平部分的总和等于容器块的宽度。假如一个 div 内有两个段落,段落的 margin 都被设置为 1embox-sizing 为默认值。每个段落的内容宽度(width 的值),加上它的左、右内边距、边框和外边距,加起来总是等于 div 的内容区的宽度。

假如 div 的宽度为 30em,那么每个段落的内容宽度、内边距、边框和外边距的总和就是 30em。在图 8 中,段落外的“空白”实际上是它们的外边距。如果 div 还有内边距,还会有更多空白,不过这里 div 没有内边距。

-c-w347

图8 元素盒的宽度与包含块的宽度一样宽

1. 水平属性

水平格式化的“七个属性”分别是:margin-leftborder-leftpadding-leftwidthpadding-rightborder-rightmargin-right。这些属性与块盒的水平布局有关,如图 9 所示。

-w408

图9 水平格式化的七个属性

这七个属性的值加在一起必须是该元素的包含块的宽度,这通常是块元素的父元素的 width 值(因为块级元素的父元素几乎都是块级元素)。

在这七个属性中,只有三个可以被设置为 auto:元素内容的 widthmargin-leftmargin-right。其余的属性必须是要么被设置为指定值,要么是默认宽度为 0。图 10 展示了盒子的哪些部分可以取值为 auto,哪些不能。

-w409 图10 可以被设置为 auto 的水平属性

width 必须设置为 auto 或者一个某种类型的非负值。如果在水平格式化中使用 auto,会得到不同的效果。

2. 使用 auto

如果将 widthmargin-left 或者 margin-right 设置为 auto,并且给余下的两个属性指定特定值,那么被设置为 auto 的属性会确定所需的长度,从而让元素盒的宽度等于父元素的宽度。换句话说,假如七个属性的总和必须等于 500px,没有设置内边距或者边框,margin-right 和 width 被设置为 100pxmargin-left 被设置为 auto。那么 margin-left 将是 300px 宽:

div {width: 500px;}

p {
    margin-left: auto; /* margin-left 等于 300px */
    margin-right: 100px;
    width: 100px;
}

从某种程度上讲,可以用 auto 弥补实际值与所需总和的差距。不过,如果这三个属性都被设置为 100px,都没有被设置为 auto,会怎么样呢?

如果所有这三个属性都被设置为非 auto 的某个值,或者,按照 CSS 的术语来讲,当这些格式化属性过份受限时,那么 margin-right 总会被强制设置为 auto。这意味着,如果 margin 和 width 都被设置为 100px,那么浏览器会将 margin-right 重置为 automargin-right 的实际宽度会根据有一个 auto 值时的规则来设置,即由这个 auto 值填补所需的距离,使元素的总宽度等于其包含块的宽度。图 11 展示了如下标记的结果:

div {width: 500px;}
p {
    margin-left: 100px; 
    margin-right: 100px;/* margin-right被强制为300px */
    width: 100px;
}

-w312 图 11. 覆盖 margin-right 设置

如果 margin-leftmargin-right 都被显式设置,并且 width 被设置为 auto,那么这个 width 值将设置为某个值,从而达到所需要的总宽度(即父元素的内容宽度)。如下标记的结果展示在图 12 中:

p {
    margin-left: 100px; 
    margin-right: 100px; 
    width: auto;
}

-w294 图12. 自动宽度

图 12 中所示的情况最常见,因为这等价于只设置外边距,而没有为 width 做任何声明。如下标记的结果与图 12 展示的结果一模一样:

p {
    margin-left: 100px; 
    margin-right: 100px; /* same as before */
}

你可能想知道,如果 box-sizing 被设置为 padding-box 会发生什么。上面的讨论趋向于假设用的是默认值 content-box,但是这里描述的原则也同样适用于其它值,这就是为什么本节只讨论 width 和两边的外边距,而不引入内边距或者边框的原因。本节中和下面的小节中处理 width: auto时,都不会考虑 box-sizing 的值。虽然定义有 box-sizing 的盒子内布局的细节有所不同,但是对待 auto 值的方式却是相同的,因为 box-sizing 只是判断 width 是指什么,而不是决定它如何影响 margin。

3. 多个 auto

下面我们来看看如何这三个属性(widthmargin-leftmargin-right)中有两个被设置为 auto 时,会出现什么情况。如果两个外边距都被设置为 auto,如下代码所示,那么它们就会被设置为相等的长度,因为将元素在其父元素内居中,如图 13 所示。

div {width: 500px;}
p {
    width: 300px; 
    margin-left: auto; 
    margin-right: auto;
}
/* 每个 margin 都是 100px,因为 (500-300)/2 = 100 */

-w293 图 13.设置一个显式宽度

在常规流中,将两个外边距设置为相等长度是将块盒内元素居中的正确方法。(弹性盒和栅格布局中还有其它方法,但是已经超出了本文的范围)。

设置元素大小的另一种办法是将外边距之一和 width 设置为 auto。设置为 auto 的 margin 会减为 0:

div {width: 500px;}
p {
    margin-left: auto; 
    margin-right: 100px;
    width: auto;
} 
/* margin-left 等于 0; width 变为 400px */

然后 width 会被设置为所需的值,让元素占满它的包含块;在前面的示例中,它将是 400px,如图 14 所示。

-w295 图 14. 当 width 和 margin-right 都被设置为 auto 时会发生什么

最后,如果这三个属性都被设置为 auto 会出现什么情况?答案很简单:两个外边距都被设置为零,而 width 会尽可能地宽。这种结果与默认情况是相同的,即没有为 margin 或者 width 显式声明值的时候。在这种情况下,margin 默认为零,width 默认为 auto

注意,因为水平方向上的外边距不会折叠,父元素的内边距、边框和外边距会影响它的子元素。这种效果是间接的,即一个元素的外边距(等等)可能会为子元素带来偏移。如下标记的结果如图 15 所示:

div {padding: 50px; background: silver;}
p {margin: 30px; padding: 0; background: white;}

-w291 图15. 父元素的外边距和内边距带来的隐式偏移

4. 负外边距

在目前为止,可能一切看上去都很简单,你可能会奇怪为什么前面我说事情可能很复杂。好的,外边距还有一个另一面:负面。是的,给外边距设置一个负值是可能的。设置负外边距会带来一些有意思的效果。

要记住,七个水平方向属性的总和总是等于负元素的 width。只要所有属性都是大于或者等于零,那么一个元素就不可能比它的父元素的内容区大。但是,考虑如下标记,结果如图 16 所示:

div {width: 500px; border: 3px solid black;}
p.wide {margin-left: 10px; width: auto; margin-right: -50px; }

-w319 图 16.通过指定负外边距得到更宽的子元素

没错,子元素比父元素还宽!数学计算是对的:

10px + 0 + 0 + 540px + 0 + 0 - 50px = 500px

540pxwidth: auto 的计算值,需要这样一个数字与等式中余下的值相抵。尽管这导致了子元素超出了它的父元素,但是并没有违反规范,因为这七个属性加在一起仍然等于所需的总宽度。这在语义上有点牵强,但是是合法的行为。

现在,再加上一些边框混进去:

div {width: 500px; border: 3px solid black;}
p.wide {margin-left: 10px; width: auto; margin-right: -50px;
border: 3px solid gray;}

这样一来,计算出来的 width 就会减少:

10px + 3px + 0 + 534px + 0 + 3px - 50px = 500px

如果还是引入内边距,那么 width 的值会进一步减少。

相反,还可能将 margin-right: auto 的计算为负值。如果其它属性的值为满足元素不能比其包含块更宽的需求,而强制右外边距为负,就会出现这种情况。考虑:

div {width: 500px; border: 3px solid black;}
p.wide {
    margin-left: 10px; 
    width: 600px; 
    margin-right: auto;
    border: 3px solid gray;
}

等式会像如下这样计算:

10px + 3px + 0 + 600px + 0 + 3px - 116px = 500px

右外边距会计算为 -116px。即使我们已经给它一个不同的显式值,它依然会被强制为 -116px,因为规则指出:当一个元素的尺寸过份受限时,右外边距会被重置,以确保元素水平属性的总和等于父元素的内容宽度。(对于从右到左的语言,是左外边距被重置)。

下面我们考虑另一个示例,如图 17 所示,这里左外边距被设置为负:

div {width: 500px; border: 3px solid black;}
p.wide {
    margin-left: -50px; 
    width: auto; 
    margin-right: 10px;
    border: 3px solid gray;
}

-w313 图 17. 设置负左外边距

如果左外边距为负,段落不仅会超出 div 的边框,还会超出浏览器窗口的边缘!

记住,内边距、边框和内容宽度(和高度)绝对不能为负。只有外边距可以小于零。

5. 百分比

如果 width、内边距、外边距为百分比值,会应用同样的规则。值声明为长度还是百分比并不重要。

百分比可能很有用。假设我们想让一个元素的内容是其包含块宽度的三分之二,右、左内边距分别设置为 5%,左外边距为 5%,右外边距占据余下的空间。可以写为:

<p style="width: 67%; padding-right: 5%; padding-left: 5%; margin-right: auto; margin-left: 5%;">playing percentages</p>

右外边距会被计算为包含块宽度的 18%(100% - 67% - 5% - 5% - 5%)

但是,如果混合使用百分比和长度单位,可能会很麻烦。考虑如下的示例:

<p style="width: 67%; padding-right: 2em; padding-left: 2em; margin-right: auto; margin-left: 5em;">mixed lengths</p>

在这种情况下,元素的盒子可能定义如下:

5em + 0 + 2em + 67% + 2em + 0 + auto = 包含块宽度

为了让右外边距计算为零,元素的包含块必须是 27.272727em 宽(元素的内容区是 18.272727em 宽)。如果比这个宽,右外边距会计算为正值;如果比这个窄,右外边距将会是负值。

如果像下面这样,再加入长度-值一致类型(百分比、px、em、auto 并存),情况会变得更复杂:

<p style="width: 67%; padding-right: 15px; padding-left: 10px;
        margin-right: auto;margin-left: 5em;">more mixed lengths</p>

并且,只是为了事情更复杂,边框不能接受百分比值,只能是长度值。基本原则是:只使用百分比是不可能创建完全灵活的元素的,除非你愿意避免使用边框,或者使用一些更创新的方法,比如弹性盒布局。

6. 可替换元素

到目前为止,我们已经介绍了常规文本流中非可替换块盒的水平格式化。块级可替换元素管理起来更简单点。非可替换块的所有规则同样适用,只有一个例外:如果 widthauto,那么元素的 width 是内容的原生宽度。如下示例中的图像将是 20px 宽,因为这是原始图像的宽度:

<img src="smile.svg" style="display: block; width: auto; margin: 0;">

如果实际图像的宽度是 100px,那么它就是 100px 宽。

可以为 width 指定一个特定值,来覆盖这个规则。假如我们修改前面的示例,让相同的图像显示三次,每一次的 width 值都不同:

<img src="smile.svg" style="display: block; width: 25px; margin: 0;">
<img src="smile.svg" style="display: block; width: 50px; margin: 0;">
<img src="smile.svg" style="display: block; width: 100px; margin: 0;">

结果如图 18 所示。

图 18. 改变可替换元素的宽度

注意,元素的高度也会增加。如果一个可替换元素的 width 与它的原始宽度不一致,那么 height 值也会成比例变化,除非 height 自己也被设置为一个特定值。反过来也是一样:如果 height 设置了,但是 widthauto,那么 width 会随着 height的变化成比例调整。

现在既然谈到了 height,下面我们就来学习常规流块盒的垂直格式化。

二)垂直格式化

像水平格式化一样,块盒的垂直格式化也有自己的一套有意思的行为。元素的内容决定了元素的默认高度。内容宽度也会影响内容的高度;例如,段落越窄,相应就会越高,以便容纳其中行内内容。

在 CSS 中,可以对任何块级元素设置显式高度。如果你这样做,结果行为会取决于几个其它因素。假设指定高度大于显式内容所需高度:

<p style="height: 10em;">

在这种情况下,多余的高度会产生一个视觉效果,就好像有额外的内边距一样。但是,如果高度小于显示内容所需的高度:

<p style="height: 3.33em;">

当这种情况发生时,浏览器应该提供一种方式来查看所有的内容,而不是增加元素盒的高度。如果元素的内容比它的盒子高,那么浏览器的实际行为会取决于 overflow 属性的值。两个可选的方案如图 19 所示。 -w388 图 19. 高度与元素内容高度不匹配

在 CSS1 中,如果一个元素不是可替换元素(比如图像),浏览器会忽略除 auto 以外的所有其它 height 值。在 CSS2 及以后版本中,height 值不能忽略,只有一种特定情况例外,即涉及到百分比值。稍后我们将会讨论这种情况。

width 一样,height 默认定义了内容区的高度,而不是可见元素盒的高度。元素盒子顶部或者底部的内边距、边框或者外边距都会增加到 height 值,除非 box-sizing 的值不是 content-box

1. 垂直属性

与水平格式化的情况一样,垂直格式化也有七个相关的属性:margin-topborder-toppadding-topheightpadding-bottomborder-bottommargin-bottom。这些属性如图 20 所示。

图 20.Figure 20. 垂直格式化的七个属性

这七个属性的值必须等于块盒的包含块的 height。这通常是块盒的父元素的 height 值(因为块级元素的父元素几乎都是块级元素)。

这七个属性中,只有三个属性看诶有被设置为 auto:元素的 heightmargin-topmargin-bottom。内边距和边框的顶部和地步必须设置为特定值,或者是默认为零宽度(假设没有声明 border-style)。如果已经设置了 border-style,那么边框的厚度会被设置为 medium(这个值的定义并不明确)。图 21 展示了如何记住盒子中哪些部分可以有 auto 值,哪些部分不可以。

图 21. 可以设置为 auto 的垂直属性

有趣的是,如果常规流中,一个块盒的 margin-top 或者 margin-bottom 被设置为 auto,它们都会自动计算为 0。不幸的是,值为 0 就不能很容易将常规流中的盒子在它们的包含块中居中。这也意味着,如果你将一个元素的 margin-topmargin-bottom 设置为 auto,实际上它们都会重置为 0,并且从元素盒中删掉。

对于定位元素以及弹性盒元素,margin-topmargin-bottom 设置为 auto 时的处理有所不同。

height 必须被设置为 auto 或者某种类型的非负值;它永远不能小于零。

2. 百分比高度

我们已经看到如何处理长度-值的高度,下面花点时间在百分比高度上。如果常规流中块盒的 height 被设置为百分比值,那么这个值就是块盒的包含块的高度的一个百分比。假如有如下标记,最终段落的高度将是 3em

<div style="height: 6em;">
    <p style="height: 50%;">Half as tall</p>
</div>

因为设置上下外边距会让它们的高度变为零,所以在这种特殊情况下,要把一个元素垂直居中的唯一方法就是将它们都设置为 25%,并且即使这样,盒子是居中的,但是盒子中的内容并不会。

但是,如果包含块的高度没有显式声明,百分比高度就会被重置为 auto。如果将上例中 divheight 设置为 auto,段落将与 div 本身的高度完全相同:

<div style="height: auto;">
    <p style="height: 50%;">NOT half as tall; height reset to auto</p>
</div>

这两种可能性在图 22 中做了展示(段落边框和 div 边框之间的空白是段落的上下外边距)。

图 22. 不同情况下的百分比高度

在继续之前,先看看图 22 中的第一个例子,半高段落。它可能是一半高,但是不是垂直居中的。这是因为包含它的 div6em 高,也就是说半高段落是 3em 高。它的上下外边距是 1em,所以它的整个盒子高度是 5em。也就是说,在段落可见盒的下部与 div 的下边框之间实际上是 2em 的间隔,而不是 1em。咋看可能会看起来有点古怪,但是一旦你研究细节,就会明白。

3. auto 高度

在最简单的情况下,height: auto 的常规流块盒是被渲染为其高度刚好足以包含它的行内内容(包括文本)的行盒。如果常规流块盒的 heightauto,并且只有块级子元素,那么它的默认高度将是最上面的块级子元素的边框的外边缘顶部,到最下面的块级子元素的边框的外边缘底部之间的距离。因此,子元素的外边距会“超出”包含它们的元素(这种行为将在下一节解释)。

但是,如果块级元素有上或者下内边距,或者上或者下边框,那么它的高度就是从它最上面的子元素的上外边距边缘顶部,到它最下面的子元素的下外边距边缘底部之间的距离:

<div style="height: auto; background: silver;">
    <p style="margin-top: 2em; margin-bottom: 2em;">A paragraph!</p>
</div>
<div style="height: auto; border-top: 1px solid; border-bottom: 1px solid; background: silver;">
    <p style="margin-top: 2em; margin-bottom: 2em;">Another paragraph!</p>
</div>

这两种行为都在图 23 中展示出来了。

-w230 图 23. 有块级子元素,且 height 为 auto

如果将上例中的 border 改为 padding,对 div 高度的作用还是相同的:依然会把段落的外边距包含在内。

4. 垂直外边距合并

垂直格式化的另一个重要的特征是垂直相邻外边距的合并。这种合并行为只应用于外边距。如果存在内边距和边框,是绝对不会合并的。

列表条目一个接着一个的无序列表是外边距合并的一个完美的例子。假设如下声明的列表有五个条目:

li {margin-top: 10px; margin-bottom: 15px;}

每个列表条目有 10px 的上外边距和 15px 的下外边距。但是,当列表被渲染时,相邻列表条目之间的距离是 15px 而不是 25px。之所以这样子,是因为,相邻外边距会沿着垂直轴合并。换句话说,两个外边距中较小的那个会被较大的那个合并。图 24 展示了合并和没有合并的外边距之间的区别。

-w383 图 24. 合并和未合并的外边距

正确实现了的浏览器会合并垂直相邻的外边距,如图 24 中第一个列表所示,这里每个列表条目之间有 15px 的间隔。第二个列表展示如果浏览器没有合并外边距会出现什么情况,此时列表条目之间有 25px 的间隔。

如果你不喜欢合并(collapse)这个词,也可以用重叠(overlap)。尽管外边距不会真正重叠,但是你可以用打个比方来看看出现什么情况。

假设每个元素(比如一个段落)是一张小纸条,上面写着元素的内容。每张纸外面有一些透明塑料,代表外边距。第一张纸(假设是一个 h1)放在画布上。第二张纸(假设是一个段落)放在第一张纸下面,然后向上滑动,直到一张纸的塑料边缘碰到另一张纸的边缘。如果第一张纸底部边缘的塑料是半英寸,第二张纸顶部的塑料边缘是三分之一英寸,然后当它们滑到一起时,第一张纸的塑料边缘会碰到第二张纸的顶部边缘。现在这两张纸都放在画布上,它们的塑料边是重叠的。

当多个外边距相遇时,也会出现合并,如列表的最后。对前面的例子做个补充,假设应用如下规则:

ul {margin-bottom: 15px;}
li {margin-top: 10px; margin-bottom: 20px;} 
h1 {margin-top: 28px;}

列表中最后一个条目的 margin-bottom20pxulmargin-bottom15px,后面的 h128px。所以,一旦这些外边距合并了,li 的末尾和 h1 开始之间的距离就是 28px,如图 25 所示。

-w407 图 25. 外边距合并详解

现在回顾一下上一节的例子,介绍说:包含块上的边框或者内边距会导致它的子元素的外边距包含在包含块内。为了了解这种行为,我们可以给上例中的 ul 添加一个边框:

ul {margin-bottom: 15px; border: 1px solid;} 
li {margin-top: 10px; margin-bottom: 20px;} 
h1 {margin-top: 28px;}

做出这种修改后,li 元素的 margin-bottom 现在放在它的父元素(ul)内部了。因此,只有 ulh1 之间会发生外边距合并,如图 26 所示。

-w394 图 26. 增加了边框后的合并

5. 负外边距和折叠

负外边距确实对垂直格式化有影响,并且会影响外边距如何合并。如果两个垂直外边距都为负,那么浏览器会取这两个外边距绝对值中的最大值。如果两个外边距一个为正,一个为负,那么就会从正外边距减去负外边距的绝对值。换句话说,负值要与正值相加,结果值就是元素之间的距离。图 27 提供了两个具体的例子。

-w291 图 27.负垂直外边距示例

注意,上、下外边距为负时,有一种“拉近“的效果。实际上,这与负水平外边距导致元素超出其父元素没有什么区别。考虑:

p.neg {margin-top: -50px; margin-right: 10px; margin-left: 10px; margin-bottom: 0; border: 3px solid gray;}
<div style="width: 420px; background-color: silver; padding: 10px; margin-top: 50px; border: 1px solid;">
<p class="neg"> A paragraph.</p>
A div. </div>

如图 28 所见,段落只是被它的负上外边距向上拉了。注意,在标记中跟在段落后面的 div 的内容也被向上拉了 50px。实际上,跟在段落后的常规流的每一块也被向上拉了 50px

图 28. 负上外边距的效果

现在将如下标记与图 29 中所示的情况比较一下:

p.neg {
    margin-bottom: -50px; 
    margin-right: 10px; 
    margin-left: 10px; 
    margin-top: 0;
    border: 3px solid gray;
}
<div style="width: 420px; margin-top: 50px;"> 
<p class="neg">A paragraph.</p>
</div> 
<p>The next paragraph.</p>

-w242 图 29. 负下外边距的效果

图 29 中实际发生的是,跟在 div 后的元素会根据 div 底部的位置放置。正如你所看到的,div 的末尾实际上是在它的子元素(段落)的视觉底部之上。div 后的下一个元素与 div 的底部的距离是对的。根据我们看到的规则,确实应该如此。

现在我们考虑一个例子,一个列表条目、一个无序列表和一个段落的外边距都合并了。在这里,无序列表和段落都指定了负外边距:

li {margin-bottom: 20px;} 
ul {margin-bottom: -15px;} 
h1 {margin-top: -18px;}

两个负外边距中较大的那个(-18px)与最大的正外边距(20px)相加,得到 20px -18px = 2px。因此,列表条目内容的底部与 h1 内容的顶部之间只有 2px,如图 30 所示。

-w397 图 30. 正负外边距合并详解

当元素由于负外边距相互重叠时,很难区分哪个元素在上面。你可能也已经注意到本节中没有一个例子使用背景色。如果使用了背景色,其内容可能会被后面元素的背景色覆盖。这是一种可以预见的行为,因为浏览器总会按照从开始到结尾的顺序渲染元素,所以文档中后出现的常规流元素可能会覆盖较早的元素(假设这两个元素最后重叠了)。

三)列表条目

列表条目有一些它们自己的特殊规则。它们通常前面有一个标志,比如一个小点或者数字。这个标志实际上并不是列表条目内容区的一部分,所以像图 31 所示的效果是常见的。

-w202 图 31. 列表项的内容

CSS1 对这些标志相对于文档布局的放置和效果讲的很少。CSS2 引入了专门为解决这个问题而设计的一些属性,比如 marker-offset。但是,光有想法但是缺乏实现,导致这个属性在 CSS2.1 中又被去掉了。现在又正在把这些想法重新引入 CSS。因此,标志的放置是不在前端工程师的控制范围内,至少本书写的时候如此。

与一个 li 元素关联的标志是在列表条目的内容之外,还是被当作为内容开始处的一个行内标志,取决于 list-style-position 属性的值。如果标志放在内部,那么这个列表条目相对于其相邻列表条目就如同一个块级元素一样,如图 32 所示。

-w194 图 32. 标志放在列表内部和外部

如果标志放在内容之外,那么它会放在与内容左边界有一定距离的位置上(对于从左向右读的语言)。不管如何改变列表的样式,标志与内容边界的距离都不变。有时,标志会被放在列表元素本身之外,如图 32 所示。

译者注:此图与 《CSS权威指南》第三版不同

记住,list-item 盒为它们的祖先盒定义包含块,就像普通块盒一样。

译者注:为它们的祖先盒定义包含块?

四、行内元素

除了块级元素,行内元素就是最常见的了。为行内元素设置盒子属性,会带我们进入到一个更有意思的领域。行内元素有一些很好的示例,比如 em 标记、a 标记、img 标记,前两个都是非可替换元素,最后一个是可替换元素。

注意,本节描述的所有行为都不适用于 table 元素。CSS2 为处理表格和表格内容引入了一些新元素和行为,这些元素的表现与块级或者行内元素格式化大相迳庭。表样式化超出了本书的范围,它令人吃惊地复杂,并且有自己的一套规则。

非可替换和可替换元素在行内上下文中的处理方式有所不同,在讨论行内元素的构造时,我们将分别进行讨论。

一)行布局

首先,需要理解行内内容是如何布局的。它没有块级元素那样简单、直观,块级元素只是生成块盒,通常不允许其他内容与这些盒子并存。作为对比,看看块级元素,比如段落。你可能会问,所有文本行是怎么放在这里的?是谁在控制它们的摆放?我如何影响它?

为了理解行是如何生成的,首先考虑这样一种情况,一个元素包含一段很长的文本行,如图 33 所示。注意,这里我们用一个 span 元素将整行包起来,然后将 span 元素的边框样式为如下,这样就给该行加了一个边框:

span {border: 1px dashed black;}

-w375 图 33. 单行的行内元素

图 33 展示了行内元素包含在一个块级元素的最简单的情况。从方式来将,这与包含两个词的段落没什么不同。唯一的区别是,在图 34 中,我们有很多单词,并且大多数段落不会包含像 span 这类的显式行内元素。

-w387 图 34. 多行的行内元素

从只包含单行的简单情况,到我们熟悉的包含多行的情况,我们要做的只是判断元素应该有多宽,然后将行分为几段,让各段刚好都可以放到元素的内容宽度中。因此,我们就得到了如图 34 所示的状态。

什么都没变。我们所做的只是将单行分成多段,然后将这些段一个挨着一个堆叠起来。

在图 34 中,每行文本的边框也刚好与每行的顶部和底部相吻合。这只是因为没有为行内文本设置内边距。注意,边框实际上相互稍微有点重叠;例如,第一行的下边框刚好在第二行的上边框之下。这是因为边框实际上是画在每行外面的下一个像素上(假设你用的是显示器)。因为各行是相互挨着的,它们的边框就会像图 34 那样重叠。

如果我们修改 span 的样式,让它有一个背景色,那么各行的实际放置就会变得很清晰。考虑图 35,这里包含有四个段落,每个段落的 text-align 都是不同的,并且每个段落的行都有背景色。

-w367 图 35. 有不同对齐方式的行

正如我们所看到的,并非每一行都会挨着它的父元素段落的内容区的边缘,这个边缘已经用灰色虚边框指示了。对于向左对齐的段落,行都被压到靠着段落的左内容边缘对齐,每一行在换行的地方结尾。向右对齐的段落刚好相反。对于居中对齐的段落,各行的中心与段落的中心对齐。

最后一种情况下,text-align 的值是 justify,每一行必须与段落的内容区的宽度一致,这样行的边缘就挨着段落的内容边缘。行的自然长度与段落的宽度之间的差额要通过修改每行中的字符间距和词间距来弥补。因此,word-spacing 的值在文本是分散对齐时会被覆盖。(letter-spacing 的值如果是一个长度值,就不能被覆盖)。

这就很好地说明了在最简单的情况下,行是如何生成的。但是,你将会看到,行内格式化模型远非这么简单。

二)基础术语和概念

在进一步学习后面的内容之前,我们先回顾一下行内布局的一些基础术语,这对学习后面的小节非常重要:

匿名文本 匿名文本是指未包含在一个行内元素内的任何字符串。因为,在标记 <p> I'm <em>so</em> happy!</p> 中,I'mhappy! 是匿名文本。注意,空格也是匿名文本的一部分,因为空格像任何其它字符一样,也是一个字符。

Em 盒 Em 盒是在给定的字体中定义,也称为字符盒。实际的字形(glyph)可能被它们的 em 盒更高或者更矮。在 CSS 中,font-size 的值决定了每个 em 盒的高度。

内容区 在非可替换元素中,内容区可以是二者之一,CSS 规范允许浏览器选择其中一种。内容区可以是元素中每个字符的 em 盒串在一起描述的盒子;可以是元素中字符字形描述的盒子。在本书中,为简化的目的,我采用 em 盒定义。在可替换元素中,内容区是元素的原生高度,加上外边距、边框和内边距。

行间距 行间距(leading)是 font-size 值和 line-height 值的差。这个差实际上要分为两半,平均应用到内容区的上部和下部。毫不奇怪,这些添加到内容区的部分,称为半间距(half-leading)。行间距只适用于非可替换元素。

行内盒 行内盒是由行间距添加到内容区所描述的盒子。对于非可替换元素,一个元素的行内盒的高度刚好等于 line-height 的值。对于可替换元素,一个元素的行内盒的高度刚好等于内容区的高度,因为行间距不会应用到可替换元素上。

行盒 行盒是包含在行中出现的行内盒的最高点和最低点的最短的盒子。换句话说,行盒的上边缘沿着最高行内盒顶的顶部放置,行盒的下边缘沿着最低行内盒底的底部放置。

除了上面的术语和定义列表外,CSS 还包含有一套行为和有用的概念:

  • 内容区类似于一个块盒的内容盒。
  • 行内元素的背景应用于内部区及所有内边距。
  • 行内元素的所有边框都包围着内容区和所有外边距及边框。
  • 非可替换元素的内边距、边框和外边距对行内元素或其它们生成的盒子都没有垂直效果;即,它们不会影响一个元素的行内盒子的高度(并且因此也不会影响包含该元素的行盒的高度)。
  • 可替换元素的外边距和边框确实会影响该元素的行内盒的高度,并且可能会影响包含该元素的行盒的高度。

还有一点要注意:行内盒在行中是根据它们的 vertical-align 属性值来垂直对齐的。

在继续学习之前,我们先看看如何逐步构造一个行盒,通过这个过程我们可以看到行的不同部分如何共同决定行的高度。

通过遵循如下步骤,可以决定行中每个元素的行内盒的高度:

  1. 找到每个行内非可替换元素和不属于后代行内元素的文本的 font-sizeline-height 值,用 line-height 减去 font-size,从而得到盒子的行间距。这个行间距再分成两半,分别应用到每个 em 盒的上部和下部。
  2. 找到每个可替换元素的 heightmargin-topmargin-bottompadding-toppadding-bottomborder-top-width 以及 border-bottom-width 值,将它们相加。
  3. 对于每个内容区,算出它超出整行的上下基线分别多少。这可不是一件容易的任务:你必须知道每个元素、每个匿名文本以及行本身的基线的位置,然后将它们都对齐。此外,可替换元素的下边缘要放在整行的基线之上。
  4. 对于设置有 vertical-align 属性值的任何元素,确定其垂直偏移量。垂直偏移量会告诉你元素的行内盒会被向上或者向下移动多远,并且它会改变元素超出基线上下的距离。
  5. 现在你知道了所有行内盒会放在哪里,再计算最后的行盒高度。为此,只需要把基线和最高行内盒顶部之间的距离,加上基线和最低行内盒底部之间的距离。

下面我们详细考虑整个过程,这对于聪明地设置行内内容的样式是很关键的。

三)行内格式化

首先,要知道所有元素都有 line-heitght,不管是否显式声明它。这个值会显著地影响行内元素显示的方式,所以要特别注意。

现在我们来确定如何判断行的高度。行的高度(或者行盒的高度)是由它的组成元素和其它内容(比如文本)的高度决定的。line-height 实际上只影响行内元素和其它行内内容,不会影响块级元素,至少不会直接影响块级元素。理解这一点很很重要的。我们可以为一个块级元素设置 line-height,但是这个值将只在应用到块级元素内的行内内容时,才会有视觉影响。例如,考虑如下空段落:

<p style="line-height: 0.25em;"></p>

由于没有内容,该段落没有任何显示,所以我们什么都看不到。这个段落的 line-height 可以是任何值(比如 0.25em 或者 25in),但是如果没有创建行盒的内容,那就没有什么区别。

我们当然可以为一个块级元素设置一个 line-height 值,并将这个值应用到块内的所有内容,不管你内容是否包含在行内元素中。那么,从某种角度上讲,包含在块级元素内的每个文本行本身就是行内元素,不管它是否用行内元素标记包围起来。如果你喜欢,可以像下面这样写一个虚拟的标记序列:

<p>
    <line>This is a paragraph with a number of</line>
    <line>lines of text which make up the</line>
    <line>contents.</line>
</p>

尽管 line 标记并不存在,但是段落表现得好像真有这些标记一样 -- 每个文本行都从段落继承了样式。因此,你只需要为块级元素创建 line-height 规则,而不必显式地为它的所有行内元素声明 line-height

虚拟的 line 元素确实可以说明在块级元素上设置 line-height 会有怎样的行为。根据 CSS 规范,在块级元素上声明 line-height,会为该块级元素的内容设置一个最小行盒高度。因此,声明 p.spacious {line-height: 24pt;} 意味着每个行盒的最小高度是 24pt。从技术上讲,只有行内元素的内容才能继承这个行高。因此,如果假装每行都被这个虚拟的 line 元素包含着,这个模型就能很好地工作了。

四)行内非可替换元素

在格式化知识基础上,下面我们讨论只包含非可替换元素(或者匿名文本)的行构造。此后,才能更好理解行内布局中非可替换和可替换元素之间的区别。

1. 创建盒子

首先,对于行内非可替换元素或者匿名文本,font-size 的值决定了内容区的高度。如果一个行内人元素的 font-size15px,那么内容区的高度就是 15px,因为元素中所有 em 盒的高度都是 15px,如图 36 所示。

-w307 图 36. Em 盒决定内容区高度

下一个要考虑的事情是元素的 line-height 值,以及它与 font-size值的差。如果一个行内非可替换元素的 font-size15pxline-height21px,那么二者的差就是 6px。浏览器将六个像素分成两半,分别应用到内容区的上部和下部,这就生成了行内盒。这个过程如图 37 所示。

-w306 图 37. 内容区加上行间距等于行内盒

假设有如下标记:

<p style="font-size: 12px; line-height: 12px;">
This is text, <em>some of which is emphasized</em>, plus other text<br>
which is <strong style="font-size: 24px;">strongly emphasized</strong>
and which is<br>
larger than the surrounding text.
</p>

在本例中,大部分文本的 font-size 都是 12px,只有一个在行内非可替换元素中的文本的是 24px。但是,所有文本的 line-height 都是 12px,因为 line-height 是可继承属性。所以,strong 元素的 line-height 也是 12px

因此,对于 font-sizeline-height 都是 12px 的每段文本,内容高度不会改变(因为 12px 和 12px 的差是零),所以行内盒的高度是 12px。但是,对于 strong 文本,line-heightfont-size 的差是 -12px。这个差分成两半确定半间距(-6px),然后半间距分别加到内容高度的上、下部,就得到行内盒。因为这里加的都是负数,行内盒最终就是 12px 高。12px 的行内盒在元素的 24px 内容高内是垂直居中的,所以行内盒实际上比内容区小。

迄今为止,听上去好像我们对文本的各个部分做的事情都一样,而且所有的行内盒都是相同大小,但是并非如此。第二行中的行内盒,虽然是相同大小,但是实际上并没有对齐,因为文本都是按照基线对齐的(如图 38)。

-w306 图 38. 行中的行内盒

由于行内盒决定了整个行盒的高度,其相互位置是很重要的。行盒被定义为行中最高的行内盒的顶端到最低的行内盒的底部之间的距离,并且每个行盒的顶部挨着上一行的行盒的底部。图 38 所示的结果给我们带来图 39 中所示的段落。

-w298 图 39. 段落中的行盒

正如我们在图 39 中看到的,中间的行比其它两个更高一些,但是依然不足以大到包含行内所有的文本。匿名文本的行内盒决定了行盒的底部,同时,strong 元素的行内盒的顶部则设置行盒的顶部。因为行内盒的顶部是在 strong 元素的内容区以内,所以该元素的内容就溢出到行盒的外面,从而实际上与其它行盒重叠了。结果是文本行看起来不规则。

稍后,我们将探索解决这种行为的方法,以及实现基线间隔一致的方法。

2. 垂直对齐

如果我们修改行内盒的垂直对齐,会应用相同的高度确定原则。假如将 strong 元素的垂直对齐设置为 4px

<p style="font-size: 12px; line-height: 12px;">
This is text, <em>some of which is emphasized</em>, plus other text<br>
which is <strong style="font-size: 24px; vertical-align: 4px;">strongly
emphasized</strong> and that is<br>
larger than the surrounding text.
</p>

这个小改动会把 strong 元素上升四个像素,把它的内容区和行内盒向上推。因为 strong 元素的行内盒顶部已经是行中最高的,这种垂直对齐的变化也会把行盒的顶部向上推四个像素,如图 40 所示。

-w279 图 40. 垂直对齐影响行盒的高度

下面我们考虑另一种情况。这里,stong 文本所在行上还有另一个行内元素,它的对齐没有设置为基线对齐:

<p style="font-size: 12px; line-height: 12px;">
This is text, <em>some of which is emphasized</em>,<br>
plus other text that is <strong style="font-size: 24px;">strong</strong>
and <span style="vertical-align: top;">tall</span> and is<br>
larger than the surrounding text.
</p>

现在我们有与前面的示例相同的结果,即中间的行盒比其它行盒高一点。但是,请注意图 41 中 tall 文本是如何对齐的。

-w223 图 41. 行内元素与行盒对齐

在这种情况下,tall 文本的行内元素的顶部与行盒的顶部对齐。因为 tall 文本的 font-sizeline-height 有相等的值,所以它的内容高度和行盒是相同的。但是,考虑如下情况:

<p style="font-size: 12px; line-height: 12px;">
This is text, <em>some of which is emphasized</em>,<br>
plus other text that is <strong style="font-size: 24px;">strong</strong>
and <span style="vertical-align: top; line-height: 2px;">tall</span> and is<br>
larger than the surrounding text.
</p>

由于 tall 文本的 line-height 比它的 font-size 小,所以该元素的行内盒比它的内容区小。这会改变文本本身的放置,因为行内盒的顶部必须与该行的行盒的顶部对齐。因此,会得到如图 42 所示的结果。

-w218 图 42 文本再一次超出行盒

另一方面,可以将 tall 文本的 line-height 实际设置为比它的 font-size 大一些。例如:

<p style="font-size: 12px; line-height: 12px;">
This is text, <em>some of which is emphasized</em>, plus other text<br>
that is <strong style="font-size: 24px;">strong</strong>
and <span style="vertical-align: top; line-height: 18px;">tall</span>
and that is<br>
larger than the surrounding text.
</p>

因为 tall 文本指定了 line-height18px,所以 line-heightfont-size 的差是六个像素。三个像素的半间距被加到内容区,导致行内盒的高度为 18px 高。这个行内盒的顶部与行盒的顶部对齐。类似地,如果 vertical-align 值为 bottom,就会把行内元素的行内盒的底部与行盒的底部对齐。

关于本章用到的术语,vertical-align 各个关键词值的效果为:

  • top: 将元素的行内盒的顶部与包含该元素的行盒的顶部对齐。
  • bottom: 将元素的行内盒的底部与包含该元素的行盒的底部对齐。
  • text-top: 将元素的行内盒的顶部与父元素的内容区的顶部对齐。
  • text-bottom: 将元素的行内盒的底部与父元素内容区的底部对齐。
  • middle: 将元素的行内盒的垂直中点与父元素基线上 0.5ex 处对齐。
  • super: 将元素的内容区和行内盒向上移。上移距离没有指定,可能随浏览器不同而不同。
  • sub: 与 super 相同,只不过元素是向下移。
  • : 将元素向上或者向下移动一定距离,这个距离由元素的 line-height 值声明的百分比定义。

3. 管理 line-height

在前面的小节中,我们已经看到,改变一个行内元素的 line-height 会导致一行内的文本相互重叠。不过,在每种情况下,这种修改都是针对单个元素的。所以,如何以一种更通用的方式来影响元素的 line-height,而不避免内容重叠呢?

一种方法是用 em 单位结合 font-size 有改变的元素。例如:

p {line-height: 1em;}
big {font-size: 250%; line-height: 1em;}
<p>
Not only does this paragraph have "normal" text, but it also<br> contains a line in which <big>some big text</big> is found.<br> This large text helps illustrate our point.
</p>

通过为 big 元素设置 line-height,就增加了整个行盒的高度,从而为显示这个 big 元素提供了足够的空间,这样就不会与其它文本重叠,也不会应该段落中所有行的 line-height。我们使用 1em 值,所以 big 元素的 line-height 将会被设置为与 bigfont-size 相同的大小。记住,line-height 是相对于元素本身而不是父元素的 font-size 设置。结果如图 43 所示。

-w243 图 43. 为行内元素指定 line-height 属性

一定要真正理解前面几节,因为如果再添加边框,问题会更复杂。假设我们想在任意超链接周围加上五个像素的边框:

a:link {border: 5px solid blue;}

如果我们没有设置一个足够大的 line-height 来容纳这个比阿奴昂,那么就有覆盖其它行的危险。可以用 line-height 加大未访问链接的行内盒的大小,就像前例中对 big 元素的做法一样;在这种情况下,我们只需要让 line-height 的值比这些链接的 font-size10px。但是,如果不知道字体大小是多少像素,这将是很困难的。

另一种解决方案是,增加段落的 line-height。这将会影响整个元素的每一行,而不只是加了边框的超链接出现的那一行:

p {line-height: 1.8em;}
a:link {border: 5px solid blue;}

因为每行的上、下都增加了额外的空间,所以超链接周围的边框不会覆盖其它行,如图 44 所示。

-w221 图 44. 增加 line-height 为行内边框留出空间

当然,这种方法在这里是可行的,因为所有文本的大小都是相同的。如果行中还有其它元素改变了行盒的高度,边框情况可能也会改变。考虑如下:

p {font-size: 14px; line-height: 24px;} a:link {border: 5px solid blue;}
big {font-size: 150%; line-height: 1.5em;}

根据这些规则,段落内 big 元素的行内盒的高度将是 31.5px14 × 1.5 × 1.5),这也是行盒的高度。为了让基线间隔一致,必须让 p 元素的 line-height 等于或者大于 32px

基线和行高

每个行盒的实际高度取决于它的组件元素之间如何对齐。这种对齐趋向于很依赖于基线落在各个元素(或者匿名文本的各部分)中的哪各位置,因为这个位置决定了行内盒如何摆放。每个 em 盒内基线的位置对于不同的字体是不同的。这个信息是内置在字体文件中的,除非直接编辑字体文件,否则是没有任何办法可以修改的。

因此,一致的基线间隔更像一门艺术,而不是科学。如果就用一种单位,比如 em,来声明所有字体大小和行高,就有得到一致的基线间隔的机会。但是,如果混合使用多种单位,就会困难得多,甚至不可能。在写作本书时,为了让创作人员能够保证一致的基线间隔,而不管行内内容是什么,已经提出了很多属性提案,这会极大地简化在线字体的某些方面。但是,这些建议的属性还都没有具体实现,所以其采纳还遥遥无期。

4. 缩放行高

事实证明,设置 line-heigt 的最佳方法是使用跟一个原始数字作为值。之所以说这种方法是最佳的,是因为这个数字会称为缩放因子,并且这个因子是一个继承值而不是计算值。假设我们想让一个文档中的所有元素的 line-height 是它们的 font-size1.5 被。可以这样声明:

body {line-height: 1.5;}

这个 1.5 的缩放因子从一个元素向下传递到另一个元素,在每一个层级上,这个因子都被作为乘数与每个元素的 font-size 相乘。因此,如下的标记会像如图 45 所示一样显示:

p {font-size: 15px; line-height: 1.5;} small {font-size: 66%;}
big {font-size: 200%;}
<p>This paragraph has a line-height of 1.5 times its font-size. In addition, any elements within it <small>such as this small element</small> also have line-heights 1.5 times their font-size...and that includes <big>this big element right here</big>. By using a scaling factor, line-heights scale
to match the font-size of any element.</p>

-w287 图 45. 对 line-height 使用缩放因子

在本例中,small 元素的行高是 15pxbig 元素的行高是 45px(这些数看上去有点过份,不过它们与整个页面设计是一致的)。当然,如果不想让 big 文本生成太多额外的行间距,可以为它设置一个 line-height 值,从而覆盖继承的缩放因子:

p {font-size: 15px; line-height: 1.5;} 
small {font-size: 66%;}
big {font-size: 200%; line-height: 1em;}

另一种解决方案(可能是最简单的一种)是,设置样式,让所有行不比容纳其内容所需的高度高。这里就可能用 line-height1.0。这个值乘以每个 font-size,其结果与每个元素的 font-size 值完全相同。因此,对于每个元素,行内盒将与内容区相同,这意味着会使用所需的绝对最小大小来包含每个元素的内容区。

大多数字体在字符字形之间还显示有一点空间,因为字符通常比它们的 em 盒小一些。只有script (“cursive”) 字体例外,它的字符字形通常比它们的 em 盒子要大一些。

5. 添加 Box 属性

从前面的讨论已经知道,内边距、外边距和边框都可以应用于行内非可替换元素。行内元素的这些方面完全不会影响行盒的高度。如果要给一个没有外边距或者内边距的 span 元素添加边框,会得到如图 46 所示的结果。

-w290 图 46. 行内边框和行盒布局

行内元素的边框边缘是由 font-size 而不是 line-height 控制的。换句话说,如果一个 span 元素的 font-size12pxline-height36px,它的内容区就是 12px 高,并且边框将会包围着内容区。

或者,我们可以给行内元素指定内边距,这回将边框从文本本身拉开:

span {padding: 4px;}

注意,这个内边距不会改变内容高度的实际形状,所以它不会影响该元素行内盒的高度。类似地,给一个行内元素添加边框也不会影响行盒生成和布局方式,如图 47 所示。

-w285 图 47. 内边距和边框不改变 line-height

至于外边距,实际上讲,它们不会应用到一个行内非可替换元素的上下部,所以它们不会影响行盒的高度。元素的左右两端则是另外一码事。

应该还记得,行内元素基本上会作为一行放置,然后分成多个部分。所以,如果我们将外边距应用到一个行内元素,这些外边距会出现在该元素的开头和结尾:分别是左、右外边距。

内边距也会出现在边缘上。因此,尽管内边距、外边距(以及边框)不会影响行高,但是它们依然会通过将文本推离其左右两端,从而影响一个元素的内容的布局。实际上,如果左右外边距为负,就可能把文本向行内元素拉近,甚至会导致重叠,如图 48 所示。

-w252 图 48. 行内元素两端的内边距和外边距

可以把行内元素当作是一个纸片,周边有一些塑料边。在多行上显示行内元素,就像是把一个大纸片剪成一些小纸片一样。但是,每个小纸片上不会增加额外的塑料边。唯一的塑料边还是最初那个大纸片上的塑料边,所以看上去只是原来纸片(行内元素)的开头和结尾出现塑料边。至少,这是默认的行为,但是我们将看到,还有另一个选项。

所以,如果行内元素有背景和足够大的内边距,从而导致行的背景重叠,此时会发生什么呢?看下面的示例:

p {
    font-size: 15px; 
    line-height: 1em;
}
p span {
    background: #FAA; 
    padding-top: 10px; 
    padding-bottom: 10px;
}

span 元素内的所有文本都有一个 15px 高的内容区,并且我们为每个内容区的上部和下部都应用了 10px 的内边距。这个额外的像素不会增加行盒的高度,这原本很好,但是这里有背景色。所以,我们得到了如图 49 所示的结果。

-w213 图 49. 行内背景重叠

CSS 2.1 明确地指出,行盒按文档的顺序绘出:“这会导致后续行的边框在前面行的边框和文本之上绘制”。这个原则同样也适用于背景,如图 49 所示。另一方面,CSS2 允许浏览器切掉边框和内边距区域(即,不渲染它们)。因此,具体结果可能很大程度上取决于浏览器遵循哪个规范。

6. 改变换行行为

在前面小节中,我们看到当一个行内非可替换元素被分拆到跨越多行时,它就被当作好像是一个长的单行元素,这个长行被切成更小的盒子,一个换行一个切片。这实际上只是默认行为,可以通过属性 box-decoration-break 来改变。

-c

默认值 slice,是我们在前面小节中看到的。另一个值 clone 会导致元素的每个片段像独立的盒子一样画出来。这是什么意思呢?比如图 50 中的两个示例,这两个示例的标记和样式相同,除了一个是 slice,一个是 clone

图 50. slice 和 clone 的行内片段

有些区别相当明显,但是有些可能更细小。明显的效果之一是将内边距应用到每个元素的片段上,包括换行发生的两端。类似地,边框是分别围绕着每个片段绘制,而不是被分开。

更精细地,注意二者之间 background-image 位置的变化。在 slice 版本中,背景图像沿着其它一切切开,意味着只有一个片段会包含原始图像。但是,在 clone 版本中,每个背景都充当自己的副本,所以每个都有它自己的原始图像。这意味着,例如,即使我们有一个不重复的背景图像,它也会在每个片段中出现一次,而不是只在一个片段中出现。

box-decoration-break 属性经常用在行内盒上,但是它实际上可以用在元素中有换行的任何条件下。例如,在分页媒介中分页中断一个元素时。在这种情况下,每个片段就是一个独立的切片。如果我们设置 box-decoration-break: clone,那么每个盒片段就复制相同的边框、内边距、背景等等。多列布局也是如此:如果一个元素被列换行分开,box-decoration-break 会影响它渲染的方式。

7. 字形与内容区

你可能会尽力避免让行内非可替换元素的背景重叠。尽管如此,这种情况还是可能会发生,这主要取决于使用哪种字体。问题存在一个字体的 em 盒与它的字符字形之间可能存在差别。事实证明,大多数字体的字符字形的高度与它的 em 盒的高度都不一致。

这听起来也许很抽象,但是它有很实际的后果。在 CSS2.1 中,我们找到如下的话:“内容区的高度应该基于字体,但是本规范不会指定如何做。浏览器也许...使用 em 盒或者字体的最大上伸高度和最大下伸高度。(后者会确保部分超出或者低于 em 盒的字形依然落在内容区内,但是会导致不同的字体有不同大小的盒子。)“。

换句话说,一个行内非可替换元素的”绘制区“是留给浏览器的。如果浏览器采用 em 盒子为内容区的高度,那么一个行内非可替换元素的背景将等于 em 盒的高度(即 font-size 的值)。如果浏览器使用字体的最大上伸和下伸高度,那么背景可能被 em 盒更高或者更低。因此,你可以设置行内非可替换元素的 line-height1em,但是依然会让它的背景覆盖其它行的内容区。

五)行内可替换元素

行内可替换元素,比如图像,一般被认为有固有的高度和宽度;例如,一个图像的高度和宽度可能是某个像素数。因此,有固有高度的可替换元素可能会导致行盒比正常更高一些。这不会改变行中任何元素的 line-height 值,包括可替换元素本身。相反,只是让行盒的高度足够容纳可替换元素,以及所有盒属性。换句话说,整个可替换元素(包括内容、外边距、边框、内边距)都被用来定义元素的行内盒。如下样式就会得到这样一个例子,如图 51 所示:

p {font-size: 15px; line-height: 18px;}
img {height: 30px; margin: 0; padding: 0; border: none;}

-w254 图 51. 可替换元素可以增加行盒的高度,但是不会增加 line-height 的值

尽管有所有这些空白,但是段落或者图像本身的 line-height 有效值并没有因此发生改变。line-height 对图像的行内盒没有影响。由于图 51 中的图像没有内边距、外边距和边框,所以它的行内盒等于它的内容区,在这里就是 30px 高。

然而,行内可替换元素依然有一个 line-height 值。为什么呢?在最常见的情况下,它需要这个值,这样在垂直对齐时就可以正确定位元素。例如,要记住,vertical-align 的百分比值要相对于元素的 line-height 来计算。因此:

p {font-size: 15px; line-height: 18px;} 
img {vertical-align: 50%;}
<p>the image in this sentence <img src="test.gif" alt="test image"> will be raised 9 pixels.</p>

line-height 的继承值会导致图像上升九个像素,而不是其它数字。如果没有 line-height 值,它就不可能完成百分比值指定的垂直对齐。对于垂直对齐来说,图像本身的高度无关紧要;line-height 的值才是最重要的。

但是,对于其它可替换元素,将 line-height 的值传递到该可替换元素内的后代元素上可能很重要。SVG 图像就是一个例子,它使用 CSS 对图像中的所有文本设置样式。

1. 添加盒模型属性

有了以上的学习,看上去给行内可替换元素应用外边距、边框和内边距似乎很简单。

内边距和边框像平常一样应用到可替换元素;内边距围绕着具体内容插入空间,边框围绕这内边距。这个过程的不寻常之处在于,这两件事情实际上会影响行盒的高度,因为它们都是一个行内可替换元素的行内盒的一部分(不同于行内非可替换元素)。考虑图 52,该图是从如下样式得到的结果:

img {height: 50px; width: 50px;}
img.one {margin: 0; padding: 0; border: 3px dotted;} 
img.two {margin: 10px; padding: 10px; border: 3px solid;}

-w264 图 52. 为行内可替换元素添加内边距、边框和外边距来增大其行内盒。

注意,第一个行盒的高度足以包含这个图像,而第二行的高度足以包含图像以及其内边距和边框。

外边距也包含在行盒中,但是它们有自己的问题。设置正外边距没什么神秘的;它只是让可替换元素的行内盒更高。与此同时,设置负外边距也有类似的效果:它会减少可替换元素的行内盒的大小。如图 53 所示,我们可以看到负上外边距会把推向上面的一行向下拉:

img.two {margin-top: -10px;}

-w254 图 53. 行内可替换元素有负外边距的效果

当然,负外边距在块级元素上也用同样的操作。在这种情况下,负外边距会让可替换元素的行内盒比正常大小更小一些。负外边距是导致行内可替换元素挤入其它行的唯一办法,而且它也是可替换的行内元素生成的盒子经常被假定为 inline-block 的原因。

2. 可替换元素和基线

现在你可能已经注意到了,行内可替换元素默认位于基线上。如果给可替换元素添加下内边距、内边距或者边框,那么内容区会向上移(假设 box-sizing: content-box)。可替换元素实际上没有自己的基线,所以最好的办法是将它的行内盒的下部与基线对齐。因此,实际上是外边距的下外边缘与基线对齐如图 54 所示。

-w263 图 54. 行内可替换元素位于基线上

这种基线对齐方式会导致一个意想不到(而且不受欢迎)的后果:如果一个表格单元中只有一个图像,这个图像要让表格单元足够的高,才能把包含该图像的行盒包含在内。即使没有具体的文本,甚至没有空白符,包含图像的表格单元还会发生这种大小调整。因此,已经使用了多年的分片图像和间隔 GIF 设计在现代浏览器中可能会表现得很糟糕。(我知道你不会创建这些东西,但是它依然是解释这种行为的最方便的环境)。考虑如下最简单的情况:

td {font-size: 12px;}
<td><img src="spacer.gif" height="1" width="10"></td>

在 CSS 行内格式化模型下,表格单元将是 12px 高,图像放在单元格基线上。所以图像下面可能有三个像素的空间,上面有八个像素的空间。不过确切的距离取决于使用的字体系列以及基线的位置。

这种行为并不仅限于表格单元内的图像;只要一个行内可替换元素是跨即元素或者表格单元元素的唯一后代,那么都会发生这种行为。例如,div 内的一个图像也会放在基线上。

对于这种情况,最常用的解决方案是,让表格单元中的图像变成块级,这样就不会生成一个行盒。例如:

td {font-size: 12px;} img.block {display: block;}
<td><img src="spacer.gif" height="1" width="10" class="block"></td>

另一个可能的纠正方案是把包含图像的表格单元的 font-sizeline-height 都设置为 1px,这会让行盒的高度与它内部一个像素的图像的高度一样。

在本书编写时,很多浏览器会忽略这个 CSS 行内格式化模型。更多信息,请参看文章 “Images, Tables, and Mysterious Gaps”

行内可替换元素位于基线上还有量一个有意思的效果:如果应用一个负下外边距,元素实际上会被向下拉,因为它的行内盒的下部会比它的内容区的下部更高一些。因此,如下的规则会到如图 55 所示的结果:

p img {margin-bottom: -10px;}

-w249 图 55. 负的下外边距会把行内可替换元素向下拉

这很容易导致可替换元素被挤入到后面的文本行中,如图 55 所示。

行内格式化模型的历史 CSS 行内格式化模型看上去也许是有些没有必要的复杂,而且在某些方面,甚至与创作人员的意愿相违背。遗憾的是,我们现在要创建的是这样一种样式语言,它既要能向后兼容 CSS 之前的 Web 浏览器,又要为将来能扩展到更复杂的领域敞开大门,从而让过去和现在笨拙地混合在一起。这种复杂性正是这样做的直接后果。它也是另外一个原因带来的后果,即:做一些合理的决策来避免不期望的效果,同时有带来另一个不期望的效果。

例如,有图像和垂直对齐文本的行会"散开",究其原因,要追究到 Mosaic 1.0 的做法。在该浏览器中,段落中的任何图像都会留出足够大的空间来包含该图像。这种做法很好,因为可以避免图像与其它行中的文本重叠。所以在 CSS 引入为文本和行内元素设置样式的方法时,它的创作者尽力创建这样一个模型,(默认地)不会导致行内图像与其它文本行重叠。但是,同样的模型也意味着存在另外的一些问题,比如上标元素(sup)很可能也会让文本行拉开距离。

这种效果让一些创作人员很恼火,他们希望基线之间的距离应该固定,不过考虑另一种情况。如果 line-height 要求基线之间的距离是指定的,最后很可能使行内可替换元素和垂直移动元素与其它文本行重叠 - 这也会让创造人员不满意。幸运的是,CSS 有足够强大的功能,总能以这样或者那样的某种方式得到你想要的效果,CSS 的将来还有更大潜力。

五、inline-block 元素

inline-block 看上去是一个混合物,实际上也确实如此。行内块元素实际上是块级元素和行内元素的混合体。这个 display 值是 CSS 2.1 中引入的。

行内块元素作为一个行内盒,与其它元素和内容有关。换句话说,它就像一个图像一样放在一个文本行中,实际上,它会作为一个可替换元素放在行中。这意味着行内块元素的底部会默认放在文本行的基线上,而且内部没有换行。

在行内块元素内部,内容被像块级元素一样格式化。就像所有块级元素或者行内可替换元素一样,可以给行内块元素应用 widhtheightbox-sizing 属性。这些属性如果比它们包围的内容高,就会增大行的高度。

下面我们看看一些示例标记,这会帮助更清楚说明这一点:

<div id="one">
This text is the content of a block-level level element. Within this
block-level element is another block-level element. <p>Look, it's a block-level
paragraph.</p> Here's the rest of the DIV, which is still block-level.
</div>
<div id="two">
This text is the content of a block-level level element. Within this
block-level element is an inline element. <p>Look, it's an inline
paragraph.</p> Here's the rest of the DIV, which is still block-level.
</div>
<div id="three">
This text is the content of a block-level level element. Within this
block-level element is an inline-block element. <p>Look, it's an inline-block
paragraph.</p> Here's the rest of the DIV, which is still block-level.
</div>

对以上标记,我们应用如下规则:

div {margin: 1em 0; border: 1px solid;}
p {border: 1px dotted;}
div#one p {display: block; width: 6em; text-align: center;}
div#two p {display: inline; width: 6em; text-align: center;} div#three p {display: inline-block; width: 6em; text-align: center;}

这个样式标的结果如图 56 所示。

-w224 图 56. 行内块元素的行为

注意,在第二个 div 中,行内段落给格式化为正常的行内内容,就是说 widthtext-align 被忽略了(因为它们不能应用到行内元素)。但是,对第三个 div,行内块段落则尊重这两个属性,因为它被格式化为块级元素。这个段落的外边距还强制它的文本行要更高一些,因为它好像是一个可替换元素一样影响行高。

如果行内块元素的 width 没有定义或者显式声明为 auto,那么元素盒会收缩以适应内容。即,元素盒的宽度刚好足够容纳内容,而没有多余的空间。行内盒也会这样做,不过行内盒会跨多个文本行,而行内块元素不会。因此,以下规则应用到前面的标记示例时:

div#three p {display: inline-block; height: 4em;}

会创建一个较高的盒子,它的宽度刚好能包含内容,如图 57 所示。

-w206 图 57. 行内盒自动调整大小

行内块元素可以很有用,比如,如果有五个超级链接,我们喜欢它们在一个工具栏内宽度相等。为了让它们分别占据父元素宽度的 20%,但是依然保持为行内元素,可以这样声明:

nav a {display: inline-block; width: 20%;}

弹性盒是实现这种效果的另一个种方式,在大多数情况下它可能更适合,但是并非所有情况下。

六、run-in 元素

CSS2 引入了值 run-in,这个值是另一个有趣的块/行内混合体,可以让某些块级元素成为下一个元素的行内部分。这种功能对于某些标题效果很有用,这在打印排版中很常见,即标题作为文本段落的一部分出现。

在 CSS 中,只需改变 display 的值,并使下一个元素盒成为块级,就可以让一个元素成为 run-in 元素。注意,这里我谈到的盒子,不是元素本身。换句话说,元素是块级元素还是行内元素不重要,重要的是元素生成的盒子。比如,strong 元素设置为 display: block 会生成一个块级盒;p 元素设置为 display: inline 会生成一个行内盒。

所以,这里重申:如果一个元素生成一个 run-in 盒,然后该盒后面是一个块盒,那么该 run-in 元素将会成为块盒开始处的一个行内盒。例如:

<h3 style="display: run-in; border: 1px dotted; font-size: 125%;
    font-weight: bold;">Run-in Elements</h3>
<p style="border-top: 1px solid black; padding-top: 0.5em;">
Another interesting block/inline hybrid is the value <code>run-in</code>,
introduced in CSS2, which has the ability to take block-level elements and make them an inline part of a following element. This is useful for certain heading effects that are quite common in print typography, where a heading will appear as part of a paragraph of text.
</p>

因为 p 元素跟在 h3 后面,生成一个块级盒,所以 h3 元素会变成在 p 元素的内容开始处的行内元素,如图 58 所示。

-w279 图 58. 让标题成为 run-in 元素

注意这两个元素的边框是如何放置的。在这种情况下使用 run-in 的效果与使用一下标记完全相同:

<p style="border-top: 1px solid black; padding-top: 0.5em;">
<span style="border: 1px dotted; font-size: 125%; font-weight: bold;">Run-in Elements</span> Another interesting block/inline hybrid is the value <code>run-in</code>, introduced in CSS2, which has the ability to take block-level elements and make them an inline part of a following element. This is useful for certain heading effects that are quite common in print typography, where a heading will appear as part of a paragraph of text.
</p>

但是,run-in 盒与这个标记示例之间还是有点小区别。即使 run-in 盒被格式化为另一个元素内的行内盒,它们依然会从文档中的父元素中,而不是将它们放置进的元素中继承属性。下面我们再来扩展一下示例,在最外面加一个 div 和一些颜色:

    <div style="color: silver;">
    <h3 style="display: run-in; border: 1px dotted; font-size: 125%;
    font-weight: bold;">Run-in Elements</h3>
    <p style="border-top: 1px solid black; padding-top: 0.5em; color: black;">
    Another interesting block/inline hybrid is the value <code>run-in</code>,
    introduced in CSS2, which has the ability to take block-level elements and make
    them an inline part of a following element.
    </p>
    </div>

在这种情况下,h3 将是银色而不是黑色,如图 59 所示。这是因为它在插入到段落中之前,从它的父元素继承了颜色值。

-w276 图 59. run-in 元素从原来的父元素继承属性

要记住的重要的事情是,只有在 run-in 盒后面是块级盒时,run-in 才起作用。如果不是,那么 run-in 盒本身将成为块级盒。因此,假如有如下标记,h3 将保留或者甚至会变成块级,因为 table 元素的 display 值是(非常奇怪) table

    <h3 style="display: run-in;">Prices</h3>
    <table>
    <tr><th>Apples</th><td>$0.59</td></tr>
    <tr><th>Peaches</th><td>$0.79</td></tr>
    <tr><th>Pumpkin</th><td>$1.29</td></tr>
    <tr><th>Pie</th><td>$6.99</td></tr>
    </table>

创作人员不太可能将值 run-in 应用到一个自然的行内元素上,但是一旦发生这样情况,这个元素肯定会生成一个块级盒。例如,如下标记中的 em 元素会变成块级,因为后面没有跟一个块级盒:

<p>
This is a <em>really</em> odd thing to do, <strong>but</strong> you could do it if you were so inclined.
</p>

在本书编写时,只有极少数浏览器提供对 run-in 的支持。

七、计算值

如果一个元素是浮动元素或者定位元素,那么 display 的计算值会改变。如果为一个根元素声明 display,那么它的计算值也可以改变。实际上,displaypositionfloat 的值是以很有趣的方式相互影响。

如果一个元素是绝对定位,那么 float 的值就被设置为 none。对于浮动元素和绝对定位元素,display 的计算值是由声明值来决定的,如表 1 所示。

表 1. 浮动或者定位元素的 display 计算值

声明的值 计算值
inline-table table
inline, run-in, table-row-group, table-column, table-column-group, table- header-group, table-footer-group, table-row, table-cell, table-caption, inline-block block
所有其它 根据指定确定

对于根元素,如果声明 display 值为 inline-tabletable,都会得到计算值 table,声明为 none,则会得到相同的计算值(none)。所有其它 display 值都会被计算为 block

总结

虽然 CSS 格式化模型的某些方面乍看起来好像很不直观,但是等多熟悉一些会逐渐搞明白的。在很多情况下,最初看起来没道理甚至是荒谬的规则,最后看来都是为了防止一些奇怪的或者不期望文档显示的出现。在很多方面,块级元素很容易理解,影响它们的布局通常是简单的任务。另一方面,行内元素则可能更难管理点,因为有很多因素在发挥作用,尤其是元素是可替换还是非可替换元素。

w3ctech微信

扫码关注w3ctech微信公众号

共收到0条回复