CSS 当中的 vertical-align

CSS 当中的 vertical-align

我们在之前的章节当中介绍过了 CSS 当中的 floatdisplaypositionline-height 属性,今天我们接着上回继续来看与 line-height 联系十分紧密的 vertical-align,可以参考下面这个例子来看看 vertical-align 的基本用法(转自 MDN

还是老规矩,我们从一个示例开始进行介绍,如下

1
2
<div></div>
<div>为什么?</div>

我们页面当中有有两个 div 元素,第一个为空,第二个中间我们添加了一些文本,应用的样式如下

1
2
3
4
5
6
div{
width: 100px;
height: 100px;
border: 1px solid red;
display: inline-block;
}

那么请问,此时页面当中的显示是什么样子的?运行以后你可能会很惊讶,因为结果是下面这样的

至于解决办法,很简单,只需要给第二个 div 加上 vertical-align: top 即可,那么为什么会这样呢?这也是我们今天将要探讨的问题

vertical-align

vertical-align 用来指定行内元素(inline)或表格单元格(table-cell)元素的垂直对齐方式,也就是说,对于块级元素,vertical-align 是不起作用的,vertical-align 的属性值可以归为以下四类

  • 线类,如 baselinetopmiddlebottom
  • 文本类,如 text-toptext-bottom
  • 上标下标类,如 subsuper
  • 数值百分比类,如 10px1em5%

线类

线类当中用的较多的是 middle,但是我们还是一个一个来进行了解,先从 baseline 开始看起

baseline

baselinevertical-align 的默认值,其意思是指基线对齐,所谓基线,指的是字母 x 的下边缘(关于基线的概念可以参考之前的 line-height 章节)我们来看个例子,代码如下

1
2
3
4
5
.box {
width: 100px;
line-height: 100px;
border: 1px solid #ccc;
}
1
2
3
<div class="box">
<span class="text">文本</span>
</div>

由于 baseline 是默认值,所以可以不用写,父元素的 line-height100px,这其实是给默认空白节点设置的,我们在之前介绍过,每一个行框盒子都有一个看不见的节点,该节点继承了 line-height,因此子元素对齐于该节点的基线(可以想象成这个看不见的节点有一个字母 x,而子元素就是跟这个字母 x 的下边缘对齐),效果如下

关于 baseline 有一个需要注意的地方就是 inline-block 元素,如果一个 inline-block 元素,里面没有内联元素,或者 overflow 不是 visible,则该元素的基线是其 margin 底边缘,否则其基线就是元素里面最后一行内联元素的基线,其实就是我们开头时候看到的问题,例子如下

1
2
3
4
5
6
.text {
display: inline-block;
width: 100px;
height: 100px;
border: 1px solid #ccc;
}
1
2
3
4
<div class="container">
<span class="text">文本</span>
<span class="text"></span>
</div>

效果如下

top

对于内联元素,指的是元素的顶部和当前行框盒子的顶部对齐,对于 table-cell 元素,指的是元素的顶 padding 边缘和表格行的顶部对齐,例子如下

1
2
3
4
5
6
7
8
9
10
.box {
width: 100px;
line-height: 100px;
border: 1px solid #ccc;
}

.top {
line-height: normal;
vertical-align: top;
}
1
2
3
<div class="box">
<span class="top">文本</span>
</div>

效果如下

其中 bottom,跟 top 类似,将顶部换成底部即可,所以我们就不展开了,下面来看看 middle

middle

这个属性值用得比较多,对于内联元素是将元素盒子的垂直中点与父盒子的 baseline 加上父盒子的 x-height 的一半位置对齐,简单点说就是字母 x 的中心位置对齐,对于 table-cell 元素,指的是单元格填充盒子相对于外面的表格行居中对齐

基本上所有字体中,字母 x 的位置都是偏下一点的,font-size 越大偏移越明显,因此字母 x 中心的位置不是行框盒子的中心,也就是说 vertical-align 只能实现近似垂直居中对齐

文本类

文本类分为以下两种

  • text-top,指的是盒子的顶部和父级内容区域的顶部对齐
  • text-bottom,指的是盒子的底部和父级内容区域的底部对齐

看下面这个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.box {
width: 300px;
line-height: 100px;
border: 1px solid #ccc;
font-size: 20px;
}

.f12 {
font-size: 12px;
}

.f16 {
font-size: 16px;
}

.f20 {
font-size: 20px;
}

.text-top {
line-height: normal;
vertical-align: text-top;
width: 100px;
}
1
2
3
4
5
6
<div class="box">
<span class="f12">12px</span>
<span class="f16">16px</span>
<span class="f20">20px</span>
<img class="text-top" src="./card.jpg"/>
</div>

效果如下

所谓内容区域,可以看成是鼠标选中文字后高亮的背景色区域,上面的例子中,由于父元素设置的是 20px,所以图片的 vertical-align 设置 text-top 的时候,就可以看成是跟子元素为 20px 元素的内容区域顶部对齐

上标下标类

上标和下标对应着两个标签 supersubsuper 在上面,sub 在下面,这两个属性值在数学公式和化学表达式中用得比较多,平时我们开发几乎用不到,所以这里就不在展开了

数值百分比类

vertical-align 是支持数值的,并且兼容性也非常好,但大部分开发人员却不知道 vertical-align支持数值,对于数值,正值表示由基线往上偏移,负值表示由基线往下偏移,而百分比则是基于 line-height来计算的,百分比用得比较少,因为 line-height 一般都是开发人员给出的,这时候数值就可以精确定位元素,不需要再使用百分比再去计算一遍,使用数值的代码如下

1
2
3
4
5
6
7
8
9
10
11
.box {
width: 300px;
line-height: 100px;
border: 1px solid #ccc;
font-size: 20px;
}

.num {
line-height: normal;
vertical-align: 20px;
}
1
2
3
<div class="box">
<span class="num">文本</span>
</div>

效果如下

vertical-align 起作用的前提

vertical-align 起作用是有前提条件的,这个前提条件就是,只能应用于内联元素以及 display 值为 table-cell 的元素,在 CSS 中,有些 CSS 属性是会改变元素的 display 值的,例如 floatposition: absolute,一旦设置了这两个属性之一,元素的 display 值就是变为 block,因此 vertical-align 也就失去了作用,下面这段代码这样写就是错的

1
2
3
4
span {
float: left;
vertical-align: middle; /* 这一句是无效的 */
}

另外,更多人遇到的是以下这种无效的情况

1
2
3
4
5
6
7
8
.box {
height: 200px;
}

.box > img {
height: 100px;
vertical-align: middle;
}
1
2
3
<div class="box">
<img src="1.jpg" />
</div>

其实,不是 vertical-align 无效,而是前面所提到的行框盒子当中的空白节点引起的原因,由于父元素没有设置 line-height,所以当前空白节点的 line-height 就非常小,比图片的高度小很多,vertical-align: middle 没法发挥作用,这时给父元素一个比较高的 line-height,就会看到 vertical-align 起作用了

1
2
3
4
.box {
height: 200px;
line-height: 200px;
}

vertical-align 与 line-height 的关系

前面我们提到,vertical-align 的百分比值是根据 line-height 来计算的,但实质上只要是内联元素,这两个元素都会同时在起作用,如下例子

1
2
3
4
5
6
7
.box {
line-height: 32px;
}

.box > span {
font-size: 24px;
}
1
2
3
<div class="box">
<span>文本</span>
</div>

效果如下

从代码上看,好像父元素的高度会是 32px,但实质上父元素的高度会比 32px 还要高,原因是空白节点继承了 line-height: 32pxspan 也继承了 line-height: 32px,但两者的 font-size 不一样,这就导致了空白节点的 font-size 比较小,而 spanfont-size 比较大,也就是说它们的基线不在同一位置上,空白节点偏上一点,而 span 默认又是基线对齐,为此,span 总体会往上移以便跟空白节点的基线对齐,父元素元素就是这样被撑高了,而解决方案可以有以下几种

  • span 元素不使用基线对齐,可以改为 top 对齐
  • span 元素块状化
  • line-height 设置为 0
  • font-size 设置为 0

关于边界和 baseline

对于垂直对齐这个知识点来说最重要的就是涉及元素的 baseline,有时候元素的盒模型的上下边界也会变的很重要,在上面我们曾提到过 vertical-align 起作用的前提,也就是 display 值为 inlineinline-block 的元素(inline-table 的元素不在本文的讨论范围内),下面我们就来看看它们之间的区别

inline

如图所示有三行文字,行高的上下边界是红线,文字的上下边界是绿色的线,蓝色的线就是 baseline 了,左边文字的高度与行高是一致的,因此绿线和红线重合了,中间的行高是文字大小的两倍,而在右边,行高是文字大小的二分之一

行内元素的外边缘在行高的上边缘和下边缘这个范围内对齐,如果行高小于文字的高度也无所谓,关于 baseline 的定义可以参考 leading

inline-block

从左到右的三张图都是 inline-block 元素,不同的是,左面包含着没有脱离正常流的内容 C,中间的除了没有脱离正常流的内容以外还加了 overflow: hidden,右面的没有内容但是内容区还有高度,红线代表了 margin-box 的边界,黄色代表的是 border,绿色的是 padding,蓝色的是 content,蓝色的线代表的还是 baseline

我们可以发现 inline-block 元素的外边缘就是 margin-box 的边缘,inline-block 元素的 baseline 的位置要看该元素有没有处于正常流之内的内容,所以这里可以分为三种情况

  1. 在有处于正常流内容的情况下,inline-block 元素的 baseline 就是最后一个作为内容存在的元素的 baseline,这个元素的 baseline 的确定就要根据它自身来确定了
  2. overflow 属性不为 visible 的情况下,baseline 就是 margin-box 的下边界了
  3. 第三种情况下 baseline 还是 margin-box 的下边界

line-box

这一次我们将文字部分高亮显示,可以发现 line-box 的上边界与最高元素的上边界对齐,下边界与最低元素的下边界对齐

line-boxbaseline 是不可见的,但是可以很轻松的将它可视化出来,在行的开头添加一个字母,比如 x,这个字母的下边界默认就是 baseline 的位置,围绕着 baselineline-box 中形成了文字盒,文字盒可以被认为是没有和任何元素对齐的 line-box 中的 inline 元素,因此文字盒仅仅包含非格式化的 line-box 的文本,文字盒的边界由绿线来表示,因为文字盒是紧挨着 baseline 的,所以 baseline 的位置发生变化的话,文字盒的位置也会跟着改变(这里所提到的文字盒其实就是我们之前介绍到的空白节点)

总结起来的话有以下两点

  • 有一个区域叫做 line-box,垂直方向上的对齐都是发生在这个区域里面,它有 baseline,有文字盒,有上下边界
  • inline 元素也有 baseline 和上下边界,inline 元素是需要对齐的对象

line-box 的 baseline 的移动问题

这是一个 vertical-align 的坑,line-box 中的所有元素都会影响到 baseline 的位置,假设,一个元素按某种方式垂直对齐了,但是这种对齐方式会引起 baseline 的移动,又因为大部分的垂直对齐方式(除了 topbottom)和 baseline 有关,因此这个元素的垂直方向对齐的行为会引起该 line-box 内其他元素位置的调整,我们来看下面这个例子

一个很高的元素,其高度占满了整个 line-box,那么 vertical-align 对其实没有影响的,在它的 topbottom 之外没有空间让其移动,但是为了满足它的 vertical-align 的值,line-boxbaseline 会发生移动,左面的高元素的取值为 text-bottom,矮元素的取值为 baseline,如果右面的高元素的取值为 text-top,你会看到 baseline 跳上去了

1
2
3
4
5
6
7
<!-- 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>
1
2
3
4
5
.tall-box,
.short-box { display: inline-block }

.text-bottom { vertical-align: text-bottom }
.text-top { vertical-align: text-top }

如果把高元素的 vertical-align 设置为其他值,也能看到类似的行为,甚至将 vertical-align 设置为 bottom 或者是 top 也会让 baseline 发生移动,这很奇怪,因为这时候应该就没 baseline 什么事儿了,如下图所示

1
2
3
4
5
6
7
<!-- 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>
1
2
3
4
5
.tall-box,
.short-box { display: inline-block }

.bottom { vertical-align: bottom }
.top { vertical-align: top }

将两个更大的元素放在一个 line 里面,并且设置 vertical-align 的值让 line-boxbaseline 移动,在满足 vertical-align 数值对齐的条件下,line-box 的高度会自我调整,如左面的图,再增加第三个元素,第三个元素如果因为其 vertical-align 的设置不会超过 line-box 的边缘的话,它是不会影响到 line-box 的高度和 baseline 的位置的,如果它会超过 line-box 的边缘,那么 line-box 的高度和 baseline 的位置也会进行调整,在第二种情况下,另外两个元素的位置发生了下移

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 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>
1
2
3
4
5
6
.tall-box    { display: inline-block }

.middle { vertical-align: middle }
.text-top { vertical-align: text-top }
.text-bottom { vertical-align: text-bottom }
.text-100up { vertical-align: 100% }

使用场景

其实我们可以发现,在大部分的情况之下,使用 line-height 就可以解决 vertical-align 所遇到的问题,这么说的话是不是 vertical-align 就派不上用场了呢?

在这样的场景下可以尝试使用 vertical-align,即当一个文本后面跟着个 inline-block 的元素时,后者是对其文本的基线的,这就导致文本看上去被挤下来了一样,使用 vertical-align: top/text-top 就可以解决这个问题,这是 line-height 所解决不了的

参考

# CSS

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×