垂直对齐:你需要知道的所有事情(翻译)

Posted on Jun 6, 2019

44年前我们就把人类送上了月球, 但现在我们仍然无法在CSS中实现垂直居中。

————James Anderson 2013/7/20

前言

原文地址

Yeah😋, 让我们来讨论一下vertical-align这个CSS属性。 它经常被用于对齐彼此相邻的文本和元素。就像将图标居中与和它相邻的文本

但是它有时候看起来像一个SB,因为它所有让人摸不着头脑的规则都在起作用。 举个例子, 可能会发生这样的情况, 你改变一个元素的vertical-align属性之后, 它的对齐方式完全没有改变。 但是与其相邻的其他元素发生了改变! 这是在逗我呢!

所以,为了尽量减少将来的痛苦,我研究了W3C的CSS specifications, 以便搞清楚vertical-align的行为。

你将会在这篇文章中学到什么

  1. 内联级(inline-level)元素在线框(Line Box)中的垂直居中行为。
  2. 内联级元素和线框具有基线(Baselines),顶部(Tops)和底部(Bottoms)。
  3. 用vertical-align对齐基线,顶部和底部。
  4. 示例:如何将图标相对与相邻的文本垂直居中。
  5. 示例:基线如何移动。
  6. 示例:如何使垂直居中的元素底部没有空隙。
  7. 示例:如何消除两个对齐的元素之间的空隙。

内联级元素在线框中的垂直居中行为

vertical-align被用于对齐内联级元素 他们的display属性的计算结果为:

  • inline
  • inline-block
  • inline-table(未包含在这篇文章中)

Inline elements are basically tags wrapping text.

内联块级元素如他们的名字一般:存在于行内的块级元素。 他们拥有widthheight(或者由自己的内容决定)以及paddingbordermargin

内联级元素以行的形式排列在一起。 当元素过多无法排列在一行之内, 就会在其下方创建一个新的行。 所有的行会有一个所谓的线框(Line Box), 它包含了行的所有内容。

**注:**这里指的是每一行都有一个线框,而不是相邻的行共用一个线框。

线框的高度受其内容的大小影响。 在下面的插图中, 线框的顶部和底部用红色的虚线表示。(原文插图

在这些线框内, vertical-align负责对齐每个元素。 所以, 什么是元素对齐?

关于基线(Baselines),顶部(Tops)和底部(Bottoms)

垂直对齐最重要的参照点是所涉及元素的基线。 在某些情况下, 元素的封闭盒(enclosing box)的上下边缘也很重要。 让我们来看看每个相关类型元素的基线和外边缘的位置。

内联级元素

现在你能看到相邻的三行文本。 红线表示行高的上下边缘, 绿线表示字体的高度, 蓝线表示基线。

  • 最左侧文本的行高与字体大小相等, 因此代表字体高度的绿线和代表行高的红线重叠成了一条线。
  • 中间的文本的行高是字体大小的两倍。
  • 右侧的文本的行高是字体大小的一般。

内联级元素的外边缘与其行高的上下边缘对齐。 行高小于字体的大小无关紧要。 所以在上图中, 红线表示的是外边缘。

内联级元素的基线, 字符位于其中。 在图中由蓝线表示。 粗略的讲, 基线位于字体高度中间的某个位置。 详细信息见W3C 规范

inline-block元素

从左到右你能看到:一个具有内容(一个“C”)的inline-block元素, 一个具有内容且overflow: hiddeninline-block元素, 一个没有内容(但是指定了height)的inline-block元素。 红色表示外边距, 黄色表示边框, 绿色表示内边距, 蓝色表示内容。 在每个inline-block元素中基线都用蓝线表示。

inline-block元素的外边缘是其外边距顶部和底部的边缘。 在图例中用红线表示。

inline-blick元素的基线取决于元素是否有in-flow内容:

  • 在具有in-flow内容的情况下, inline-block元素的基线是正常流(normal flow)中最后一个内容元素的基线(图例中左侧的例子)。 最后一个元素根据自己的规则找到自己的基线。
  • 在具有in-flow内容且overflow属性的计算结果不是visiable的情况下,基线是外边距的底部边缘(图例中中间的例子)。 所以,它跟inline-block元素的底部边缘相同。
  • 如果不存在in-flow内容, 基线也是外边距的底部边缘(图例中右侧的例子)。

Line Box

你已经在上面看到过这个图例了。 这次我也画出了线框的文本框的上下边缘(绿线)和基线(蓝线)。 还给文本元素加上了灰色的背景以突出其区域。

线框的顶部于最顶部元素的顶部对齐, 线框的底部于最底部元素的底部对齐。 这在上面的图例中以红线所示的方框表示。

线框的基线是可变的:

CSS 2.1 does not define the position of the line box’s baseline.

CSS 2.1没有定义线框的基线位置。

— the W3C Specs

vertical-align这个属性上,基线(Baseline)大概是最混乱的部分。 It means, the baseline is placed where ever it needs to be to fulfil all other conditions like vertical-align and minimizing the line box’s height. It is a free parameter in the equation.

因为线框的基线是不可见的,因此它并不是显而易见的。 但是我们能很容易地使它可见。 只需要在行的开头添加一个字符, 比如我在图例中添加了一个“x”。 如果这个字符没有对齐任何地方, 默认情况下会位于基线上。

文本框(Text Box)

我们将线框基线周围的内容称之为文本框。 文本框可以简单地看作线框内没有任何对齐的inline元素。 它的高度等于父元素的字体大小。 因此, 文本框仅仅包含线框的无格式文本。 该框在上面的图例中用绿线表示。 因为文本框于基线相关联, 当基线移动时文本框也会移动。

注: 这个文本框在W3C规范中被称为strut

啊,这是最难的部分了。 现在我们知道了一切, 要把事情弄清楚,让我们快速总结一下最重要的部分:

  • 有一个区域叫做线框(Line Box),这是发生垂直对齐的区域。 他有一条基线(Baseline), 一个文本框(Text Box)和上下边缘。
  • 内联级元素。 它们是对齐的对象, 它们有一条基线和上下边缘。

用vertical-align对齐基线,顶部和底部

By using vertical-align the reference points mentioned in the list above are set into a certain relationship.

相对于线框的基线对齐

  • baseline: 元素的基线恰好于线框基线的重叠。
  • sub: 元素的基线位于线框基线下方。
  • <percentage>: 元素的基线相对于线框的基线移动的距离是相对于行高的百分比。
  • <length>: 元素的基线相对于线框的基线移动的距离是一个绝对值

一个特例是vertical-align: middle,如下图例:

  • middle: 元素上下边缘的中点于线框基线加上x高度的一半对齐。

相对于线框的文本框对齐

也可以在相对于线框的基线对齐的情况下列出这两个案例, 因为文本框的位置由基线决定(见文本框(Text Box))。

  • text-top: 元素的上边缘于线框中文本框的上边缘对齐。
  • text-bottom: 元素的下边缘于线框中文本框的下边缘对齐。

相对于线框的外边缘对齐

  • top: 元素的上边缘于线框的上边缘对齐。
  • bottom: 元素的下边缘于线框的下边缘对齐。

当然W3C中也有正式的定义

Why Vertical-Align Behaves The Way It Behaves

我们现在可以在某些情况下仔细地研究垂直对齐了。我们还会处理一些可能出问题的情况。

居中一个图标

一个困扰我的问题如下:我有一个图标, 我想在一行文本旁边居中放置它。 仅仅给图标设置vertical-align: middle似乎不是一个令人满意的居中方案。 看看这个示例:

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

这是一个相同的示例, 但是我画了一些你已经从上文中看到过的辅助线(不知道为什么人们能一眼看出这里细微的不协调):

这让我们的问题有些苗头了。 因为左边的文本完全没有对齐图标, 它跟基线对齐了。 现在的情况是,通过给图标设置vertical-align: middle我们将图标的中点和小写字母“x”的中点(即x-height的一半)对齐。所以有升部和降部的字符就会突出。

注: 升部和降部分别指字母向上超出主线和向下超出基线的部分。来自维基百科

注: x-height(译名:X高度)是一个特有名词,是指字母的基本高度,精确地说,就是基线(英语:baseline)和主线之间的距离。特别的,它指称一个字体中小写字母x的高度(这也是这个词的语源)。来自维基百科

在右边, 我们使整个字体区域对齐垂直方向上的中点。 这时文本的基线向线框的基线的下方移动了一点以对齐中点。 很明显这是一个完美的对齐的方案。

线框基线的移动

使用vertical-align时, 有一个常见的陷阱:基线的位置会被行内所有的元素影响。 让我们做一个假设, 当一个元素以某种方式对齐时, 线框的基线一定会移动。 大多数垂直对齐(除了顶部和底部对齐)都是相对于基线对齐的, 这会导致线框内的其他元素重新调整其位置。

看看这些示例:

  • 如果一个元素的高度等于整行的高度,vetical-align对这个元素是无效的。 因为没有多余的空间让它移动。 为了相对于线框的基线对齐, 线框的基线就必须移动。 较矮的盒子的vertical-align属性是baseline。 左侧较高的盒子的vertical-align属性是text-bottom。 右侧较低的盒子的vertical-align属性是text-top。 你能看到基线带着较矮盒子移动到了上方。
 1<!-- left mark-up -->
 2<span class="tall-box text-bottom"></span>
 3<span class="short-box"></span>
 4
 5<!-- right mark-up -->
 6<span class="tall-box text-top"></span>
 7<span class="short-box"></span>
 8
 9<style type="text/css">
10  .tall-box,
11  .short-box   { display: inline-block;
12                /* size, color, etc. */ }
13
14  .text-bottom { vertical-align: text-bottom; }
15  .text-top    { vertical-align: text-top; }
16</style>

就算较高元素的vertical-align属性取其他值, 也会显示如上相同的行为。

  • 即使设置vertical-align的值为bottom(左侧)或top(右侧), 基线也会移动。 这很奇怪, 因为这与基线毫无关系。
 1<!-- left mark-up -->
 2<span class="tall-box bottom"></span>
 3<span class="short-box"></span>
 4
 5<!-- right mark-up -->
 6<span class="tall-box top"></span>
 7<span class="short-box"></span>
 8
 9<style type="text/css">
10  .tall-box,
11  .short-box { display: inline-block;
12               /* size, color, etc. */ }
13
14  .bottom    { vertical-align: bottom; }
15  .top       { vertical-align: top; }
16</style>
  • 将两个较高元素放在一行内并且将他们垂直居中, 这时基线将会移动以确保两个元素的对齐条件, 同时线框的高度也会调整。 然后加入第三个元素, 这个元素vertical-align属性为middle, 且上下都没有超过线框的边缘。 因此既不会影响线框的高度也不会影响线框基线的位置。 如果它超过了线框的边缘, 那么线框高度和基线又会移动了。 在这种情况下, 前面两个元素被下来了。
 1<!-- left mark-up -->
 2<span class="tall-box text-bottom"></span>
 3<span class="tall-box text-top"></span>
 4
 5<!-- middle mark-up -->
 6<span class="tall-box text-bottom"></span>
 7<span class="tall-box text-top"></span>
 8<span class="tall-box middle"></span>
 9
10<!-- right mark-up -->
11<span class="tall-box text-bottom"></span>
12<span class="tall-box text-top"></span>
13<span class="tall-box text-100up"></span>
14
15<style type="text/css">
16  .tall-box    { display: inline-block;
17                 /* size, color, etc. */ }
18
19  .middle      { vertical-align: middle; }
20  .text-top    { vertical-align: text-top; }
21  .text-bottom { vertical-align: text-bottom; }
22  .text-100up  { vertical-align: 100%; }
23</style>

内联级(inline-level)元素下方可能会有一点小空隙

看看这个示例。 如果你尝试垂直对齐列表中的li元素很有可能碰到过这种情况。

 1<ul>
 2  <li class="box"></li>
 3  <li class="box"></li>
 4  <li class="box"></li>
 5</ul>
 6
 7<style type="text/css">
 8  .box { display: inline-block;
 9         /* size, color, etc. */ }
10</style>

如你所见, 列表项位于基线上。 基线下方留出了一些空间来容纳文本的降部。 这正是我们看到空隙的原因。 如何解决这个问题? 既然文本是与基线对齐才留出了空隙,那么我们只需要将文本与其他的位置对齐就好了。 举个例子, 将列表项设置为vertical-align: middle

 1<ul>
 2  <li class="box middle"></li>
 3  <li class="box middle"></li>
 4  <li class="box middle"></li>
 5</ul>
 6
 7<style type="text/css">
 8  .box    { display: inline-block;
 9            /* size, color, etc. */ }
10
11  .middle { vertical-align: middle; }
12</style>

具有内容的inline-block元素不会发送这种情况,因为基线已经位于内容的上方了

内联级元素之间的空隙会破坏布局

这主要是内联级元素自身的问题, 但是因为这是垂直对齐的一个要求,因此了解这一点是很好的。 (不太理解这句话, 下面是原文。)

This is mainly a problem of inline-level elements themselves. But since they are a requirement of vertical-align, it is good to know about this.

你能在第一个示例中的列表项看到空隙。 空隙来自内联元素之间的空白。 内联元素之间的所有空白被合并为一个空格。 如果我们并排放置两个内联元素并且给它们的width属性赋值50%。那么它们所占据的空间就是两个50%加上一个空格。 显然这超出了一行。 因此该行被分为两行, 这破坏了布局(左侧)。 如果要消除空隙,我们就需要删除空格,例如使用html注释(右)。

 1<!-- left mark-up -->
 2<div class="half">50% wide</div>
 3<div class="half">50% wide... and in next line</div>
 4
 5<!-- right mark-up -->
 6   <div class="half">50% wide</div><!--
 7--><div class="half">50% wide</div>
 8
 9<style type="text/css">
10  .half { display: inline-block;
11          width: 50%; }
12</style>

垂直对齐揭秘

yea😋,就是这样。 如果你知道规则,它并不复杂。 如果vertical-align的表现与预期的不一致, 就问问自己下面这些问题:

  • 线框的基线位置以及上下边缘的位置在哪?
  • 内联级元素的基线位置以及上下边缘的位置在哪?

这些问题有可能帮助你解决遇到的难题。

这里有一个更复杂的示例如何垂直居中一个vertical-align属性为middlediv