关于vertical-align属性
前言
关于vertical-align
属性被用得最多的场景就是用于对一些内联元素的垂直居中显示,然而事情永远不是我们想的那样简单,给了一个middle
就能永远如我们所愿,很多时候基本就会发现图片和文字压根就没有垂直居中对齐……这个时候基本上都不知道为啥,试了一些其他属性值之后如果未果那么基本上就会用上flex
布局暴力居中了,我一般就是这样做的  ̄□ ̄||。
vertical-align的作用
对于内联元素
内联元素包括
display: inline
、display: inline-*
和匿名内联元素(如文本节点等);
vertical-align
可以设置内联元素与其容器元素(父元素或line boxes
)在垂直方向的对齐方式;要理解对齐方式,首先要理解以下几个概念:
- 基线
x
高度inline boxes
line box
相关概念
基线和 x 高度
可以看一下维基百科的一张图[1],一目了然;x
高度指的就是小写字母x
的高度,而基线指的就是小写字母x
的底部边缘处;这张图的其他概念也扩展一下:
ascent
:升部;即高于小写x
字母以上的部分;ascent height
:升部高度;某个字符位于升部区域的高度;descent
:降部;即低于小写x
字母以下的部分;descent height
:降部高度;某个字符位于降部区域的高度;
inline boxes 和 line boxes
这里借用张老师的一张图[2],也是一目了然;
-
inline box
:其实就是各种内联元素外面包裹的一层盒子,其高度与font-size
和line-height
有关; -
line box
:顾名思义,指的就是一行内联元素所包裹的盒子,可以包含多个inline box
。可以用鼠标选中内联元素,高亮的区域的高度就是其对应的line box
的高度。
vertical-align的属性值
vertical-align
的对齐对象有两类,因此属性值作用的对象也有两类,一类是父元素,一类是line boxes
(即每一行);
对齐对象为父元素
The following values only have meaning with respect to a parent inline element, or to the strut of a parent block container element.[3]
baseline
:默认值,使内联元素盒子(inline box
,下同)基线与父元素的基线对齐;不过父元素的基线位置有多种情况,需要单独说明;middle
:使内联元素盒子的中线与父元素基线向上0.5
个x
高度的位置对齐;很明显,我们平时把middle
理解为内联元素的中线与父元素的中线对齐是错误的!text-top
:使内联元素盒子的顶部与父元素content area
的顶部对齐;text-bottom
:使内联元素盒子的底部与父元素content area
的底部对齐;sub
:使内联元素盒子的基线与父元素的下标基线对齐;super
:使内联元素盒子的基线与父元素的上标基线对齐;<length>
:使内联元素盒子的基线与父元素基线向上给定长度的位置对齐;可以为负值,负值则代表基线向下给定长度的位置;<percentage>
:与<length>
同理,百分比相对的是内联元素的line-height
;
关于 inline box
这里内联盒子的范围可以参考W3C
的官方定义:
In the following definitions, for inline non-replaced elements, the box used for alignment is the box whose height is the ‘line-height’ (containing the box’s glyphs and the half-leading on each side, see above). For all other elements, the box used for alignment is the margin box.[3:1]
简言之,对不可替换元素,这个inline box
的高度就是line-height
;反之则是margin-box
;
On a block container element whose content is composed of inline-level elements, ‘line-height’ specifies the minimal height of line boxes within the element. The minimum height consists of a minimum height above the baseline and a minimum depth below it, exactly as if each line box starts with a zero-width inline box with the element’s font and line height properties. We call that imaginary box a “strut.”[4]
关于没有基线的元素
对于没有基线的元素来说,其基线用margin-box
区域的底边缘来替代!然而对于表现形式为inline-block
的内联元素来说,其基线的确定要稍微特殊一点:当inline-block
元素内部没有任何内联子元素或者overflow
的值不为visible
时,其基线位置还是margin-box
的底边缘,否则就是其内部最后一个line box
的基线位置!
The baseline of an ‘inline-block’ is the baseline of its last line box in the normal flow, unless it has either no in-flow line boxes or if its ‘overflow’ property has a computed value other than ‘visible’, in which case the baseline is the bottom margin edge.[3:2]
关于 content area 及其顶部位置
content area
是什么?content area
的顶部位置又是如何确定的?本来我也没想过深究这些,直到我准备查看一下text-top
属性值的效果时,我简直一脸懵逼,不知道发生了什么……看个例子:
1 | .demo { |
1 | <div class="demo"> |
很明显<img>
的对齐区域应该是其margin-box
,那么为何其父元素的content area
的顶部不是<div>
的顶部?可能的解释就是,其对齐的父元素不是外层的<div>
,而是line box
!那么如何确定line box
的content area
?
关于content area
的高度,W3C
有这么一段说明:
The height of the content area should be based on the font, but this specification does not specify how. A UA may, e.g., use the em-box or the maximum ascender and descender of the font.[3:3]
据我观察,谷歌浏览和火狐浏览器应该都是采用了字体的最大升部高度和最大降部高度组成高度;即:
content area height
=x
高度 + 最大升部高度 + 最大降部高度
因为这个line box
的顶部位置正好可以通过line-height - font-size * 1.4
计算得到(这个1.4
根据具体字体可能有差异);
当content area
的高度小于line box
的高度时,那么line box
的content area
顶部就是:
line box height
-content area height
反之,那么line box
的content area
顶部就是line-box
的顶部;
可以利用上面的公式大概计算一下,怎样可以使得上述的text-top
能够对齐到<div>
的顶部,即line box
的content area
的高度等于line-height
;由于line-height
设置为50px
,所以推出font-size
为50/1.4
,约等于36px
;看下font-size
为36px
的情况:
差不多就是这个逻辑;
对齐对象为 line box
top
:内联元素及其后代的顶部与line box
的顶部对齐;bottom
:内联元素及其后代的底部与line box
的底部对齐;
除了上面的简单定义,W3C
的规范里还提到了一点:
The inline-level boxes are aligned vertically according to their ‘vertical-align’ property. In case they are aligned ‘top’ or ‘bottom’, they must be aligned so as to minimize the line box height. [5]
也就是说,使用top
和bottom
对齐时,会使得line box
的高度保持在尽量小的程度;而line box
的最小高度就是line-height
,这一点规范上也有说明:
On a block container element whose content is composed of inline-level elements, ‘line-height’ specifies the minimal height of line boxes within the element[4:1]
关于 line box 及其高度
关于line box
,W3C
有这么一段定义:
In an inline formatting context, boxes are laid out horizontally, one after the other, beginning at the top of a containing block. Horizontal margins, borders, and padding are respected between these boxes. The boxes may be aligned vertically in different ways: their bottoms or tops may be aligned, or the baselines of text within them may be aligned. The rectangular area that contains the boxes that form a line is called a line box.[6]
简言之,就是每一行上包裹这一行内所有inline box
的那个区域就是line box
;而line box
的高度则是由其内部最靠上的inline box
顶部到最靠下的inline box
底部之间的距离(因为可能各个inline box
之间的高度和垂直对齐方式不一致):
The height of a line box is determined as follows:
- …
- …
- The line box height is the distance between the uppermost box top and the lowermost box bottom. (This includes the strut, as explained under ‘line-height’ below.)[5:1]
对于单元格元素
单元格元素包括
<td>
及display: table-cell
的元素;
vertical-align
除了可以设置内联元素的垂直对齐方式外,还能用于设置单元格的垂直对齐方式,其属性值有:
baseline
:使单元格的基线与当前行中其它所有以基线对齐方式的单元格的基线对齐;top
:使单元格的padding-box
的顶边缘与当前行的顶部对齐;bottom
:使单元格的padding-box
的底边缘与当前行的底部对齐;middle
:使单元格的padding-box
居中;
(幽灵)空白节点
空白节点的特征就是:本身是一个inline box
,宽度为0
,但是具有(继承)font
和line-height
属性;因此空白节点本身是看不见的。关于这种空白节点,貌似找不到很明显的官方定义;张老师把这种空白节点称之为“幽灵空白节点”,因为这些空白节点都是无意或者html
在解析的过程中自动产生的,且不能直接控制,所以谓之“幽灵”。
理解空白节点的存在对于弄清楚之前使用vertical-align: middle
等属性值为何不生效(或者不符合预期)是必须的。
空白节点的来源
大致有两处来源:
-
标签后面换行符或空格产生的;如:
1
2
3
4
5
6
7
8.demo {
width: 200px;
background-color: lightcyan;
}
.pic {
width: 40px;
height: 40px;
}1
2
3<div class="demo">
<img class="pic" src="https://placekitten.com/40/40">
</div>可以看到图片底部留有空隙,空隙产生的原因就是
<img>
后面进行了换行,因此html
解析后多出一个空白节点;空白节点虽然宽度为0
,但是继承了line-height
属性,因此其高度就是line-height
,所以<img>
元素的margin-box
底部会与空白节点的inline box
基线进行对齐! -
line box
内部默认第一个inline box
就是空白节点;exactly as if each line box starts with a zero-width inline box with the element’s font and line height properties. We call that imaginary box a “strut”[4:2]
W3C
把这种空白节点称之为“strut”,意为“支柱”;可能是撑起行高的作用。
空白节点的影响
导致图片不能正常居中
有时候我们可能想使用line-height
等于height
加上vertical-align: middle
使图片在某区域中垂直居中这种套路,结果发现图片总是中间偏下一点:
1 | .demo { |
1 | <div class="demo"> |
这个就是空白节点捣的鬼,由于空白节点继承了line-height
,进而有了基线位置,因此line box
的基线位置就变了空白节点的基线位置,然而字体基线位置并不是在中线位置往下0.5
个x
高度的地方,而是更下一点的位置,也就是“基线下沉”。因此,此时基线往上0.5
个x
高度之后的位置并不是inline box
的中线位置,而且偏下一些的位置;至于到底偏下多少,这取决于字体及字体大小。
可以在<img>
加上一个x
来查看基线位置(可以当做“显形”的空白节点),蓝色框就是文本节点的inline box
区域,可以看到基线位置明显偏下(inline box
整体居中),因此<img>
对齐的地方根本不是inline box
的中线位置,所以就会偏下。
解决办法:虽然不能直接控制空白节点的样式,但是由于空白节点继承font
和line-height
属性,所以可以操纵外层元素的font-size
来影响空白节点的基线位置。设置外层font-size: 0
,可以使得空白节点的line-height
为0
,因此其高度也为0
,那么所有的“线”都重合了,处于line box
的中线处,所以<img>
此时对齐的位置就是line box
的中线:
其它问题
图标和文字不能垂直居中对齐
同一行中,混有图标和文字时,若使父级元素的height
等于line-height
,图标高度也等同于父级元素的高度,但是可以看到文字并非和图片一样垂直居中,而是偏下一些。如:
1 | .demo { |
1 | <div class="demo"> |
可以分析一下,由于vertical-align
的默认值就是baseline
,因此图片和文字都是基线对齐其父元素,即外层line box
的基线位置;从上图可以很明显看出,此时line box
的基线位置就是<div>
区域的底部边缘,因此文字基线就会对齐底部,而非想象中的居中。
那么把对齐方式都改为middle
又如何?
显然,由于基线下沉的影响,文字对齐的位置会相对中间线偏下一点点。
解决办法:只需要将图标的对齐方式设置为top
即可,此时图标和文字就会居中对齐:
因为top
对齐会使得line box
的高度保持在最低,此时由于inline box
的高度都没有超过line-height
,因此此时line box
的高度就是line-height
的高度。
为何修改对齐方式会导致line box基线位置变化
虽然可以说x
底部边缘位置就是line box
的基线所在位置,但是为何修改inline box
的对齐方式会导致line box
的基线位置发生改变?关键就在于如何确定line box
的基线位置,然而要命的就是CSS
规范里面竟然没有定义line box
的基线位置!!!
there are multiple solutions and CSS 2.1 does not define the position of the line box’s baseline[5:2]
看到这里我突然觉得为啥围绕着竖直对齐为何会产生那么多说不清的地方了……因为很多对齐方式基本上都是根据line box
的基线位置进行对齐,然而line box
的基线位置居然没有官方定义。
???
然而实际上浏览器在实现渲染的时候,肯定会有关于line box
基线位置的定义的,不过不知道该去哪里能找到这份定义,不然就一直在这个地方纠结不清了。
后话
为何使用vertical-align
属性的时候会心里没底,查完那么多资料我终于明白了,因为牵扯到的规范实在是太多了!
还有,这次之所以引用了那么多的W3C
规范,是因为根据MDN
的资料和网上一些文章压根不足以解答心中的疑惑。因此,去查看一手资料是最直接了当的方式,也免得道听途说误解了什么。不过这样查资料确实挺耗费时间的,但得到的收获也少不了。
参考文档
- CSS深入理解vertical-align和line-height的基友关系 « 张鑫旭-鑫空间-鑫生活
- 关于 vertical-align 你应该知道的一切 - 掘金
- 字母’x’在CSS世界中的角色和故事 « 张鑫旭-鑫空间-鑫生活
- vertical-align - CSS(层叠样式表) | MDN