重拾CSS规范之vertical-align

我经常需要让元素在垂直方向上对齐。

CSS 提供了很多可用的方法。有时候我用 float 解决问题,有时候使用 position: absolute ,而有些时候呢,甚至通过手动添加 marginpadding 这样污染代码的方式来实现。

我实在不喜欢这些解决方法。浮动元素仅在顶部对齐并且需要手动清除浮动。绝对定位使得元素脱离文档流,所以它们将再也无法影响周围元素。还有,使用固定 marginpadding 值的话,极小的改变都会立即破坏现有的布局。

但是,这里还有另一个角色: vertical-align 。我认为它应当受到重任。好了,确切地说,使用 vertical-align 来布局是一种 hack 行为,因为它不是为此而生。它用来排列文本和文本旁边的元素。尽管如此,你还是可以在不同的上下文中用 vertical-align 来灵活地、精细地排列元素。元素的大小并不需要知晓。元素会继续在文档流中,所以其他元素可以据此改变它们的尺寸。这些优势让它成为了有价值的选择。

vertical-align 的独特性

但是,某些时候 vertical-align 是个十足的混蛋,用它解决问题可能会让人沮丧,似乎有一些难以理解的规则在起作用。比如,可能会出现这样的情况,你改变了 vertical-align 属性值的的元素压根没有改变对齐方式,但同一行的其他元素却有变动。我苦恼地撕扯着头发,仍不时地陷于 vertical-align 的死角。

不幸的是,大部分有关于 vertical-align 的资料都稍显粗浅,尤其是我们想藉此进行布局的时候。它们都集中精力在这样一个误区,试图让一个元素中的所有东西垂直对齐。它们给出了这个属性的基础介绍,以及在特别简单的情况下元素如何对齐,但并未解释棘手的那部分。

因此我给自己设立了一个目标,要一劳永逸地阐述清楚 vertical-align 的行为表现,最终通过研究 W3C 组织的 CSS 规范文档 并且演练一些例子完成了。成果就是这篇文章。

所以,就让我们来讨教一下这场游戏规则吧!

使用 vertical-align 的条件

vertical-align 被用来对齐 内联级元素 ,指这些元素—— display 属性计算值为

  • inline
  • inline-block
  • inline-table(本文中不作考虑)

inline 元素基本上是包裹文字的标签。

inline-block 元素人如其名:披着 inline 外套的块元素。就像它拥有 paddingbordermargin 一样,它同样可以拥有 widthheight (可能由其自身内容所决定)。

内联级元素会在各行一个挨着一个排列开来,如果当前行有太多元素放不下的时候,新的一行会在它下面产生。所有这些行都拥有一个行盒,它包裹着该行所有内容。不同大小的内容意味着行盒拥有不同的高度。在下面的插图中,行盒的顶部和底部,都以红色线条表示。

line-box 图示

这些行盒描绘出了我们可以作用的区域。在这些行盒中, vertical-align 属性的职责是对齐一些独立的元素。 那么问题来了,元素都是相对于谁来对齐的呢?

关于基线和外边缘

垂直对齐最重要的参考点就是所涉及元素的基线,某些情况下元素的盒模型的上下边缘也很重要。就让我们来看看涉及到的各个种类的元素的基线和外边缘在哪儿。

inline 元素

内联元素的基线以及外边缘图示

这里你可以看到相连文字的三条线,行高的上下边界用红色线条,文字的高度用绿色线条,基线用蓝色线条标注出来了。左边,文字行高被设置为文字大小的高度;中间,文字行高是文字大小的两倍;右边,文字行高是文字大小的一半。

行内元素外边缘 与它行高的上下边缘对齐,如果行高小于字体的高度也无所谓,所以,在上图中外边缘是红色线条。

行内元素基线 就是字符坐着的那条线,在图中是蓝色线条。严格来说,基线就是处在文字高度中间以下的某处。好好看看 W3C 规范寻求一下详细定义

inline-block 元素

内联块元素的基线以及外边缘图示

从左到右你可以看到三个内联块元素,左边的包含着文档流内的内容(一个‘c’),中间的除此以外还添加了 overflow: hidden ,右边的没有文档流内的内容(但内容区域还有高度)。margin 边界用红色线条标注, border 用黄色, padding 用绿色,内容区域用蓝色。每个 内联块元素的基线是用一条蓝色线条展示的。

内联块盒的外边缘margin 盒的上边缘和下边缘。他们在图中是红色线条。

内联块盒的外边缘 取决于元素是否拥有文档流内的内容:

  • 在拥有流内内容的情况下,内联块元素的基线是最后一个流内内容元素的基线,这个元素的基线根据其自身规则来计算。(左边例子)
  • 在拥有流内内容并且 overflow 计算值不是 visible 的情况下,基线是 margin 盒的下边缘。这样,它就和内联块元素的下边缘一样了。(中间例子)
  • 在没有拥有流内内容的情况下,基线同样是 margin 盒的下边缘。(右边例子)

line box

行盒基线及外边界图示

这种标注你们在上面就已经看过了,这次我同样画了行盒中文本框的上下边界(绿色)和基线(蓝色)。我还给文本元素设了灰色背景来高亮显示它们。

行盒的上边界与最高元素的上边界对齐,下边界与最低元素的下边界对齐。上图中红线标注的框就是。

行盒的基线 是可变的:

CSS 2.1没有定义行盒基线的位置. — W3C 工作组

当我们使用 vertical-align 的时候,这可能是最让人疑惑的部分了。基线会被放置在任何需要的地方,用以达成像 垂直对齐 和 最小化行盒高度 的情况。它是等式中的自由变量。

行盒的基线是不可见的,它不能立马显示出它在哪里,但是可以很轻易地让它可见。在行的开头添加一个字符,比如我在例子中添加了’x’。如果这个字符没有以任何方式对齐的话,它将默认基线对齐。

行盒中,在基线周围有一个叫做 文本框 的存在。文本框可以被简单地当成行盒中的一个没有任何对齐方式的内联盒。它的高度等于它的父元素的字体大小。因此,文本框只包含行盒的非格式化文本,它在上图中被绿色线条标注。由于文本框与基线绑定在一起,当基线移动时它也移动。(这里所说的文本框在 W3C 工作组被叫做 strut

喔,这就是最难的部分,通过各个角度观察我们已经知晓了所有事情。让我们赶紧总结一下最重要的事实:

  • 这里有个叫做 line box 的区域,在这里,会有垂直对齐。它拥有一条基线,一个文本框,以及上下边界各一条。
  • 内联级元素,是会对齐的对象。它们拥有一条基线,上下边界各一条。

vertical-align 的值

上面那个列表中最后一句话提及的参考点,因为 vertical-align 而有了关联。

元素的基线与行盒的基线

元素的基线与行盒的基线图示

  • baseline:元素基线正好就在行盒基线上
  • sub:元素基线在行盒基线下方
  • super:元素基线在行盒基线上方
  • :元素基线根据行盒基线位置移动 line-height * <percentage>
  • :元素基线根据行盒基线位置移动绝对的长度

元素的外边界与行盒的基线

元素的外边界与行盒的基线图示

  • middle:元素上下边界间的中心点对齐在行盒基线加上一半x-height

元素外边界与行盒的文本框

元素外边界与行盒的文本框图示

相对于该行框的基线对齐,我们还可以列出如下这两种情况,因为文本框的位置由基准确定。

  • text-top:元素上边界与文本框上边界对齐
  • text-bottom:元素下边界与文本框下边界对齐

元素外边界与行盒外边界

元素外边界与行盒外边界图示

  • top:元素上边界与行盒上边界对齐
  • bottom: 元素上边界与行盒下边界对齐

当然,W3C 规范中就有 正式定义

为何 vertical-align 如此变现呢

我们可以在特定的情境下近距离观察下垂直对齐,尤其是,我们在处理某些bug的情况下。

围绕一个图标

一直困扰着我的一个问题是这样的:我有一个图标,我想让他紧邻一行文字居中对齐,我给它加上了 vertical-align: middle 但似乎没有以令人满意的方式居中。来看看这个例子:

icon居中图示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- left mark-up -->
<span class="icon middle"></span>
Centered?
<!-- right mark-up -->
<span class="icon middle"></span>
<span class="middle">Centered!</span>
<style type="text/css">
.icon { display: inline-block;
/* size, color, etc. */ }
.middle { vertical-align: middle; }
</style>

这里我再次展示一下这个例子,不过我画了一些辅助线,上面已经讲过了。

添加辅助线后的图示

它揭示出了我们的问题的一些情况。因为左边的文字压根没有对齐,实际情况是,使用 vertical-align 我们会把盒子对齐在没有延伸(1/2 的x-height)的小写字母的中心点。所以,有延伸的字符会伸出顶部。

在右边,文字与icon都对齐于一个中点,文字的基线稍微下移,位于行盒的基线的下方。结果是很好的达到了icon与文字对齐的效果。

行盒基线的移动

当我们使用 vertical-align 时,都有这样一个坑:行盒基线的位置受该行所有元素的影响。假设,一个元素以某种方式对齐了,行盒基线受影响移动了,由于大部分元素都已经根据这条基线垂直对齐完毕,最后的结果就是其他所有的元素都需要调整位置。

一些例子:

  • 一个很高的元素,其高度占满了整个行盒,那么 vertical-align 对其是没有影响的,在它的顶部和底部之外没有空间让其移动。但是为了满足它的对齐需求,行盒的基线会发生移动,左面的高元素的取值为 text-bottom ,矮元素的取值为 baseline 。右面的高元素的取值为 text-top ,你会看到基线跳上去了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <!-- left mark-up -->
    <span class="tall-box text-bottom"></span>
    <span class="short-box"></span>
    <!-- right mark-up -->
    <span class="tall-box text-top"></span>
    <span class="short-box"></span>
    <style type="text/css">
    .tall-box,
    .short-box { display: inline-block;
    /* size, color, etc. */ }
    .text-bottom { vertical-align: text-bottom; }
    .text-top { vertical-align: text-top; }
    </style>
当给高元素设置 `vertical-align` 为其他值时,会有同样的表现。
  • 甚至将 vertical-align 设置成 bottomtop 也会移动基线,这很奇怪,因为不该波及基线。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <!-- left mark-up -->
    <span class="tall-box bottom"></span>
    <span class="short-box"></span>
    <!-- right mark-up -->
    <span class="tall-box top"></span>
    <span class="short-box"></span>
    <style type="text/css">
    .tall-box,
    .short-box { display: inline-block;
    /* size, color, etc. */ }
    .bottom { vertical-align: bottom; }
    .top { vertical-align: top; }
    </style>

此文为译文,现在不想翻译了,以后会把它补全,并放到译文目录下,vertical-align 章节自己会写一篇文章推上来。

就是这么任性。

热评文章