垂直对齐:你需要知道的所有事情(翻译)
44年前我们就把人类送上了月球, 但现在我们仍然无法在CSS中实现垂直居中。
前言
Yeah😋, 让我们来讨论一下vertical-align
这个CSS属性。 它经常被用于对齐彼此相邻的文本和元素。就像将图标居中与和它相邻的文本。
但是它有时候看起来像一个SB,因为它所有让人摸不着头脑的规则都在起作用。
举个例子, 可能会发生这样的情况, 你改变一个元素的vertical-align
属性之后, 它的对齐方式完全没有改变。
但是与其相邻的其他元素发生了改变! 这是在逗我呢!
所以,为了尽量减少将来的痛苦,我研究了W3C的CSS specifications, 以便搞清楚vertical-align
的行为。
你将会在这篇文章中学到什么
- 内联级(inline-level)元素在线框(Line Box)中的垂直居中行为。 →
- 内联级元素和线框具有基线(Baselines),顶部(Tops)和底部(Bottoms)。→
- 用vertical-align对齐基线,顶部和底部。→
- 示例:如何将图标相对与相邻的文本垂直居中。→
- 示例:基线如何移动。→
- 示例:如何使垂直居中的元素底部没有空隙。→
- 示例:如何消除两个对齐的元素之间的空隙。→
内联级元素在线框中的垂直居中行为
vertical-align
被用于对齐内联级元素
他们的display
属性的计算结果为:
- inline
- inline-block
- inline-table(未包含在这篇文章中)
Inline elements are basically tags wrapping text.
内联块级元素如他们的名字一般:存在于行内的块级元素。 他们拥有width
和height
(或者由自己的内容决定)以及padding
, border
和margin
。
内联级元素以行的形式排列在一起。 当元素过多无法排列在一行之内, 就会在其下方创建一个新的行。 所有的行会有一个所谓的线框(Line Box), 它包含了行的所有内容。
**注:**这里指的是每一行都有一个线框,而不是相邻的行共用一个线框。
线框的高度受其内容的大小影响。 在下面的插图中, 线框的顶部和底部用红色的虚线表示。(原文插图)
在这些线框内, vertical-align
负责对齐每个元素。 所以, 什么是元素对齐?
关于基线(Baselines),顶部(Tops)和底部(Bottoms)
垂直对齐最重要的参照点是所涉及元素的基线。 在某些情况下, 元素的封闭盒(enclosing box)的上下边缘也很重要。 让我们来看看每个相关类型元素的基线和外边缘的位置。
内联级元素
现在你能看到相邻的三行文本。 红线表示行高的上下边缘, 绿线表示字体的高度, 蓝线表示基线。
- 最左侧文本的行高与字体大小相等, 因此代表字体高度的绿线和代表行高的红线重叠成了一条线。
- 中间的文本的行高是字体大小的两倍。
- 右侧的文本的行高是字体大小的一般。
内联级元素的外边缘与其行高的上下边缘对齐。 行高小于字体的大小无关紧要。 所以在上图中, 红线表示的是外边缘。
内联级元素的基线, 字符位于其中。 在图中由蓝线表示。 粗略的讲, 基线位于字体高度中间的某个位置。 详细信息见W3C 规范
inline-block元素
从左到右你能看到:一个具有内容(一个“C”)的inline-block
元素, 一个具有内容且overflow: hidden
的inline-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
属性为middle
的div