likes
comments
collection
share

CSS行内布局(三):行内级元素与vertical-align

作者站长头像
站长
· 阅读数 17

〇、前言

在上一篇文章《CSS行内布局(二):line-height》中,我们在行内布局模型的基础上,深入学习了line-height。而在这一篇,我们将继续深入行内布局,主要将涉及以下内容:

  • 行内级(inline-level)元素的分类,以及不同类别的元素在基线与布局边界上的区别
  • vertical-align中与父元素相关的规则,以及与行相关的规则
  • 如何借助font-size: 0实现真正的垂直居中

一、行内级元素

在前两篇文章中,我们讨论的都是行内元素。实际上,在行内布局中还可以有其他类型的元素,例如:<img>、<video>、inline-blockinline-flex等。但凡是display等于inline,或者带inline-*前缀的元素都能放到行内布局中,这些元素被统称为行内级(inline-level)元素

从严格意义上来说,之前我们所讨论的行内元素(如:<span>),全称应该是不可替换行内(non-replaced inline)元素,与此对应的是可替换行内(replaced inline)元素,例如:<img>和<video>。虽然可替换行内元素的display也是inline,但是其内部的样式并不完全受到CSS控制,而且其内部也无法插入文本。

另一方面,在上一篇文章中,我们提到不可替换行内元素在遇到换行时会分裂成多个片段(fragment),但是可替换行内元素和inline-block等元素,它们始终在一行,并不能分裂折行。因此,这些不能分裂成多个片段的行内级元素又称为原子行内(atomic inline)元素

不同类型的行内级元素,在行内布局中的基线位置和布局边界各不相同。故此,我将区别制成下表,配上demo一个,方便小伙伴们理解和比较。

display类别基线位置布局边界
inline不可替换行内元素由字体和字号决定由行高和内容区域计算而来
inline可替换行内元素margin框下边界margin框上下边界
inline-*inline-block如果能从内容中获取基线,则取其中最后一条基线为基线;否则,以margin框下边界为基线margin框上下边界
inline-*inline-flex、inline-grid、inline-table等如果能从内容中获取基线,则取其中第一条基线为基线;否则,以margin框下边界为基线margin框上下边界

值得一提的是,关于inline-*的元素如何从内容中获取基线,并不是说只要这些元素中的内容存在基线就可以,而且要求子元素不能脱离父元素原有的布局。所以在demo中,如果子元素是绝对定位的,那么,父元素依然无法从中获取基线。

二、vertical-align

在上一篇文章中,所有的行内元素都是按照基线对齐,也就是vertical-align: baseline。现在让我们来看一下vertical-align的其他取值。vertical-align的取值分为两类,一类是相对于父元素的某个位置对齐,另一类是相对于行框对齐。

vertical-align的取值在table中有不同的含义,本文只讨论在行内布局中的情况。

2.1 相对于父元素

取值描述
baseline使元素的基线与父元素的基线对齐
sub使元素的基线与父元素的下标基线(subscript-baseline)对齐,下标基线的位置从字体的属性中获取,否则默认为下沉父元素的字号 / 5的距离
super使元素的基线与父元素的上标基线(superscript-baseline)对齐,上标基线的位置从字体的属性中获取,否则默认为上浮父元素的字号 / 3的距离
text-top使元素的上布局边界与父元素的内容区域的上边界(也就是ascent)对齐
text-bottom使元素的下布局边界与父元素的内容区域的下边界(也就是descent)对齐
middle使元素的中线与父元素的x-height的一半的位置对齐。x-height即小写x的高度,是字体的一个内部属性之一,细节可见本系列第一篇文章中的第二章

虽然说这些取值都是相对于父元素,但是谁才是真正的“爸爸”,却并不那么显而易见。例如:

<div>
  x
  <span id="exponent1" style="vertical-align: super">
    y<span id="exponent2" style="vertical-align: super">2</span>
  </span>
</div>

表面上看,#exponent2的父元素是#exponent1,而div是#exponent1的父元素。但是在布局模型中,#exponent1真正的父亲其实是根行内框。


同样,为方便小伙伴们理解各取值的含义,下面是一个复合demo及其布局模型。其中,我重点标出了text-toptext-bottom的布局边界。

<div style="
  font-family: 'Catamaran', sans-serif;
  font-size: 50px; 
  width: 350px; 
  line-height: 100px;
  outline: 1px solid;
">
  Lorex 
  <span style="vertical-align: middle; font-size: 16px;">middle</span> 
  <span style="line-height: 40px; vertical-align: text-top; font-size: 16px;">text-top</span> 
  <span style="line-height: 40px; vertical-align: text-bottom; font-size: 16px;">text-bottom</span> 
</div>

2.2 相对于行框

取值描述
top将对齐后的子树(aligned subtree)的上布局边界与行框的上边界对齐
bottom将对齐后的子树(aligned subtree)的下布局边界与行框的下边界对齐

对于vertical-align中相对于行框对齐的topbottom两个值,并不是简单的使用元素本身的布局边界与行框进行对齐,而是需要先将该元素内部所有相对于父元素对齐的子元素进行对齐之后,再选取整个子树的布局边界,与行框进行对齐。为什么呢?因为从定义上,行框是必须要能包住其中所有的布局边界的。文字描述比较拗口,还是让我们来看一个例子:

<div style="
  font-family: 'Catamaran', sans-serif;
  font-size: 50px; 
  width: 250px;
  outline: 2px solid;
  line-height: 120px;
">
  Lorex
  <!-- 单层span | top对齐 -->
  <span id="top1" style="vertical-align: top; font-size: 16px; line-height: normal;">
    top 1
  </span>
  <!-- 双层嵌套span | top对齐 -->
  <span id="top2" style="vertical-align: top; font-size: 16px; line-height: normal;">
    top <span id="num2" style="vertical-align: super; line-height: 60px">2</span>
  </span>
  <!-- 双层嵌套span | text-top对齐 -->
  <span id="top3" style="vertical-align: text-top; font-size: 16px; line-height: normal;">
    top <span id="num3" style="vertical-align: super; line-height: 60px">3</span>
  </span>  
</div>
<div>

CSS行内布局(三):行内级元素与vertical-align

#top1#top2都是对齐于行框的上边界,但是由于#top2中的#num2的行高比#top2本身的行高更高,所以最后是以#num2的布局上边界对齐于行框的上边界。

而对于#top3,它的HTML结构与#top2完全相同,但它是相对于父元素的text-top对齐。相对于父元素对齐的值只需要考虑元素本身,不考虑整个子树,所以#top3的对齐结果没有受到其内部#num3的影响。

实际上,上面例子中行内元素多层嵌套的情况,在Chrome和Safari中的渲染结果都与CSS规范中不同,只有Firefox是准确的。 测试环境:MacOS 13.1,Chrome 108,Safari 16.2,Firefox Developer Edition 109

三、font-size: 0

在第一篇文章中提到,font-size其实是字符和行内框内容区域大小的缩放因子。所以当font-size: 0时,行内框其实就坍缩不见了。同时,行内框中的基线、x-height、ascent和descent等这些参考线也都合并到了一起。

3.1 垂直居中

借助font-size: 0的这个特性,我们可以做一些有趣的事情。上文提到,vertical-align: middle是将元素的中线与x-height的一半对齐,所以并不是真正的垂直居中。加上font-size: 0后,情况就不同了,如下例:

CSS行内布局(三):行内级元素与vertical-align

感兴趣的小伙伴可以思考一下这个例子背后的布局模型。

四、尾声

至此,本系列的内容就全部结束了。如系列开篇所言,随着CSS的快速发展,如今行内布局的使用场景已经少了许多,所以在我编写本系列文章的过程中,有时会觉得花费这么多时间是否值得?但回想当初,在我探究行内布局细节的过程中,是阅读到了其他作者编写的文章,才解答了我的疑惑。既然如此,那么我也有义务将知识继续传播下去。

希望本系列文章,能对正在阅读的你们有所帮助。