写在前面
本文第一部分翻译自Vertical-Align: All You Need To Know,就是之前在CSS上下左右居中参考资料部分提到的待翻译的那一篇
其余部分是对原文的技巧总结
一.译文
经常需要让一些并排显示的元素竖直对齐
CSS提供了一些可选方案,有时通过float
来解决,有时用position: absolute
,有时甚至用手动添加margin
或padding
这样的脏方法,我不很喜欢这些方案。浮动只是让它们顶部对齐,而且要手动清除(浮动的影响)。绝对定位让一些元素脱离标准文档流,以至于它们无法再影响周围元素。而即使是最微小的变动也会破坏固定margin
和padding
但还有另外一个角色:vertical-align
。我觉得它更值得信任。虽然在技术上,用vertical-align
实现布局是一种hack
,因为它不是为布局设计的,而是用来对齐文本与文本旁边元素的。但是,也能用vertical-align
在不同环境中灵活且细粒度(fine-grained)地对齐元素。不需要知道元素的大小,元素仍然处于标准文档流中,其它元素能响应其尺寸变化。这些优势让它成了一个有价值的选项
vertical-align的怪脾气
但vertical-align
有时候真的很讨厌,用起来会有些挫败感,似乎有一些神秘的规则。例如,可能会遇到,改变元素的vertical-align
根本没有改变它自己的对齐方式,但同一行的其它元素(的对齐方式)却变了!现在还时不时地钻进这些阴暗的角落,让我抓狂(tearing my hair)
不幸的是,大多数相关资源都太浅显了,尤其是在我们想用vertical-align
实现布局时。他们专注于试图让一个元素里面的所有东西都竖直对齐的错误想法,给出属性的基本介绍,并解释非常简单的场景下元素的对齐方式,而不解释技巧性的部分
所以,我给自己定下了一劳永逸地澄清vertical-align
行为的目标,以深入W3C的CSS规范,并尝试一些例子告终,最终成果就是本文
那么,下面我们从游戏规则入手
vertical-align的依赖项
vertical-align
用来对齐内联级(inline-level)元素,也就是那些display
属性的计算值为:
inline
inline-block
inline-table
(本文不考虑)
内联元素(inline elements)是基本标签包裹着的文本
内联-块元素(inline-block elements)就像它名字所说的那样:内嵌的块元素(block elements living inline)。它们可以具有width
,height
(也有可能是通过其内容确定的)和padding
,border
及margin
内联级元素(inline-level elements)在一行中一个挨一个地排列,一旦当前行放不下了,就在它下方创建一个新行,所有这些行都具有所谓的行盒(line box),包住这一行的所有内容。不同大小的的内容意味着不等高的行盒。下图中行盒的上下边界用红线标出来了:
行盒就是我们的上下文(the line boxes trace out the field we are playing on),这些行盒中的vertical-align
属性负责对齐各个元素。那么,元素对齐到底是怎么回事?
baseline和outer edge
竖直对齐最重要的参照点是相关元素的baseline,某些情况下,元素包裹盒的顶边和底边也很重要。我们一起看看各种类型元素的baseline和outer edge在哪里:
内联元素
可以看到3行并列的文本,行高的顶边和底边用红线表示出来,字体的高度用绿线,baseline用蓝线。左边文本的行高设置为与font-size
相同,绿线和红线重合了。中间文本行高是font-size
的2倍。右边行高是font-size
的一半
内联元素的outer edge与其行高的顶边和底边对齐,如果行高小于字体高度的话,就无所谓。所以,outer edge是上图中的红线
内联元素的baseline是字符坐在上面的那条线(baseline is the line, the characters are sitting on),即图中的蓝线。很难理解的是,baseline有时会在字体高度的下方,见W3C规范的详细定义
内联-块元素
从左到右依次是:含有流内(in-flow)内容(那个“c”)的内联-块元素,含有流内内容和overflow: hidden
的内联-块元素和不含流内内容(但内容区具有高度)的内联-块元素。margin的边界用红线表示出来,border为黄色,padding为绿色,内容区为蓝色,每个内联-块元素的baseline用蓝线表示
内联-块元素的outer edge是其margin-box的顶边和底边,也就是图中的红线
内联-块元素的baseline取决于元素是否含有流内内容:
含有流内内容时,内联-块元素的baseline是常规流中最后一个内容元素的baseline(左边的例子),最后一个元素的baseline是根据它自身的规则来确定的
含有流内内容但具有计算值为非
visible
的overflow
属性时,baseline是margin-box的底边(中间的例子),所以,它与内联-块元素的底边相同不含流内内容时,baseline也是margin-box的底边(右边的例子)
行盒
上图中,把行盒的文本盒(更多信息见下文)的顶边和底边用绿色画出来,而baseline还用蓝线,还给文本元素设置了灰色背景高亮标记出来
行盒的顶边与该行最高元素的顶边对齐,并且底边与该行最低元素的底边对齐,就是上图中用红线表示的部分
行盒的baseline是可变的:
CSS 2.1 does not define the position of the line box’s baseline. — the W3C Specs
这可能是用vertical-align
时最让人迷惑的部分了。也就是说,baseline具体放在哪里要满足所有其它条件,比如vertical-align
和让行盒高度最小,它是方程中的一个自由参数
因为行盒的baseline是不可见的,无法直观地看出来它在哪里。但很容易就能让他变得可见,只需要在有疑问的行首添一个字符,就像图中添的“x”。如果这个字符没有以任何方式对齐,它默认将坐在baseline上
在baseline周围,行盒含有我们称之为文本盒(text box)的东西。文本盒可以简单地看做一个没有任何对齐方式的行盒中的内联元素。其高度等于其父元素的font-size
。因此,文本盒只会包裹行盒中没被格式化过的文本,上图中用绿线表示出来了。因为这个文本盒与baseline绑在一起,baseline动的时候它也跟着动(注:这个文本盒在W3C规范中被称为strut)
这就是最难的部分了。现在,我们已经知根知底了。快速总结一下最重要的几点:
有个区域叫行盒,是竖直对齐发生的地方。它具有baseline,文本盒及顶边底边
内联级元素,是哪些被对齐的东西,它们具有baseline和顶边底边
vertical-align的值
通过使用vertical-align
来对上面提到的参照点和内联级元素设定某些关联
元素的baseline相对行盒baseline对齐
baseline
:元素的baseline恰好与行盒的baseline重合sub
:元素的baseline移到行盒baseline下方super
:元素的baseline移到行盒的baseline上方<percentage>
:元素的baseline相对行盒的baseline移动关于line-height
的百分比<length>
: 元素的baseline相对行盒的baseline移动一个绝对长度
元素的outer edge相对行盒baseline对齐
middle
:元素顶边底边之间的中点与行盒的baseline加上半个x-height对齐
元素的outer edge相对行盒的文本盒对齐
text-top
:元素的顶边与行盒的文本盒的顶边对齐text-bottom
:元素的底边与行盒的文本盒的底边对齐
元素的outer edge相对行盒的outer edge对齐
top
:元素的顶边与行盒的顶边对齐bottom
:元素的底边与行盒的底边对齐
当然,正式的定义在W3C规范里都能找到
为什么vertical-align的行为是这样
我们可以更近一步看看某些场景下的竖直对齐,尤其是我们将那些可能出错的场景
居中小图标
有个烦扰着我的问题:我有一个小图标,想要与旁边的一行文本居中对齐。只给小图标来个vertical-align: middle
看起来居中效果不那么让人满意。看看这个例子:
<!-- 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>
这儿还有个相同的例子,但我画出了一些你已经从上面了解到的辅助线:
这样能揭示一些线索,因为左边的文本没有任何对齐方式,它坐在baseline上。实际上,设置vertical-align: middle
来对齐小方块,我们把它对齐到了不具上伸部(ascender)的小写字母的中心位置(半个x-height)。所以,具有上伸部的字符显得比较靠上
右边的话,我们让整个字体区的中点也竖直对齐,把文本的baseline相对行盒baseline稍微下移来实现效果。结果是文本和紧挨着的小图标漂亮地居中了
行盒baseline的移动
这是个用vertical-align
的常见陷阱:行盒的baseline受该行所有元素的影响。我们假设有个元素以这种方式对齐(相对自身baseline对齐),行盒的baseline就不得不移动。因为大多数竖直对齐(除了top
和bottom
)都是相对其baseline的,导致该行所有其它元素也都跟着调整位置
一些示例:
- 如果一行有个高元素横跨整个高度,
vertical-align
对它就不起作用了,它顶部之上和底部之下已经没有能供它移动的空间了。为了满足其相对行盒baseline的对齐关系,行盒baseline就不得不移动了。矮方块具有vertical-align: baseline
,左边,高方块是text-bottom
对齐,右边是text-top
对齐,可以发现baseline带着矮盒子一起跳上去了
<!-- 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
为bottom
(左图)和top
(右图)也会移动baseline,这就怪了,因为根本不牵扯baseline啊
<!-- 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>
- 一行里放两个大元素,竖直对齐它们会移动baseline到满足它们对齐方式的位置,然后行盒的高度也会调整(左图)。添上第三个元素,其对齐方式不会让它超出行盒的边界的话,既不影响行盒的高度也不影响baseline的位置(中图)。如果它超出了行盒的边界,行盒的高度和baseline就会再次调整,这种情况下,我们最初的两个方块被推下去了(右图)
<!-- left mark-up -->
<span class="tall-box text-bottom"></span>
<span class="tall-box text-top"></span>
<!-- mark-up in the middle -->
<span class="tall-box text-bottom"></span>
<span class="tall-box text-top"></span>
<span class="tall-box middle"></span>
<!-- right mark-up -->
<span class="tall-box text-bottom"></span>
<span class="tall-box text-top"></span>
<span class="tall-box text-100up"></span>
<style type="text/css">
.tall-box { display: inline-block;
/* size, color, etc. */ }
.middle { vertical-align: middle; }
.text-top { vertical-align: text-top; }
.text-bottom { vertical-align: text-bottom; }
.text-100up { vertical-align: 100%; }
</style>
内联级元素下方可能会有小间隙
看看这种情况,试图vertical-align
列表里的li
时,很容易遇到:
<ul>
<li class="box"></li>
<li class="box"></li>
<li class="box"></li>
</ul>
<style type="text/css">
.box { display: inline-block;
/* size, color, etc. */ }
</style>
如图所示,列表项坐在baseline上,baseline下方是一些用于来容纳文本下延部(descender)的空间,造成了间隙。解决方案呢?只需要把baseline移远一点,例如,用vertical-align: middle
对齐列表项:
<ul>
<li class="box middle"></li>
<li class="box middle"></li>
<li class="box middle"></li>
</ul>
<style type="text/css">
.box { display: inline-block;
/* size, color, etc. */ }
.middle { vertical-align: middle; }
</style>
这种场景不会出现在含有文本内容的内联-块元素中,因为内容已经移到baseline上了
内联级元素之间的间隙破坏布局
这主要是内联级元素自身的问题,但因为它们是vertical-align
的依赖项之一,所以最好了解清楚
在前一个例子中也能看到列表项之间的间隙,间隙来自出现在标记代码(HTML/XML等)里的内联元素之间的空白字符。内联元素之间的所有空白字符都被合并成一个空格,就是这个空格碍事,例如想让两个内联元素仅挨在一起并都设置width: 50%
的话,就没有足够的空间容纳两个50%
的元素和一个空格。所以会拆分成2行破坏布局(左图)。为了去掉间隙,我们需要去掉空白字符,例如用HTML注释(右图)
<!-- left mark-up -->
<div class="half">50% wide</div>
<div class="half">50% wide... and in next line</div>
<!-- right mark-up -->
<div class="half">50% wide</div><!--
--><div class="half">50% wide</div>
<style type="text/css">
.half { display: inline-block;
width: 50%; }
</style>
vertical-align揭秘
嗯,就是这样,一旦知道规则后就不很复杂。如果vertical-align
不生效,只用考虑这些问题:
行盒的baseline和顶边底边在哪里?
内联级元素的baseline和顶边底边在哪里?
这将揭示问题的解决方案
二.技巧
1.怎样确定行盒的baseline?
给这一行加个没有下延部的字符,一般习惯加x
,字符的底部边缘就是行盒baseline的位置
例如:
.baseline:before {
content: 'x';
}
2.怎么确定行盒的边界?
利用上面提到的“元素的outer edge相对行盒的outer edge对齐”:
.line-box-top {
border-top: 1px dotted red;
/* 让 border-top 与行盒的顶边重合 */
vertical-align: top;
/* 宽度沾满整行 */
display: inline-block;
width: 100%;
/* 让开空间,避免影响内容布局 */
margin-right: -100%;
/* 提升z,避免被内容遮住 */
position: relative;
z-index: 10;
}
/* .line-box-bottom 与之类似 */
在想要明确行盒边界的那一行的行首(因为用margin-right: -100%
,所以放最左边)添上
<span class="line-box-top"></span><span class="line-box-top"></span>
即可
3.怎么确定文本盒的边界?
与确定行盒边界的方法类似,利用vertical-align: text-top;
和vertical-align: text-bottom;
相对谁对齐,那么就能把这个“谁”画出来
4.用HTML注释去掉空白字符技巧
例如:
<figure>
<span class="large font">
<span class="green dotted line text-top"> </span><!--
--><span class="green dotted line text-bottom"> </span><!--
--><span class="red dotted line top"> </span><!--
--><span class="red dotted line bottom"> </span><!--
--><span class="blue dotted line baseline"> </span><!--
--><span class="font color-grey inline-overlay">x</span><!--
--><span class="center">
<span class="middle bg-grey">This</span>
<span class="tall box bg-grey text-top"> </span>
<span class="top bg-grey">can</span>
<span class="tall box bg-grey text-bottom"> </span>
<span class="bottom bg-grey">happen.</span>
</span>
</span>
</figure>
去掉空白字符的同时,保留标签缩进格式,很有意思