CSS进阶(7)—— 内联元素的掌管者line-height和vertical-align(上)

1,395 阅读13分钟

在CSS世界中,块级元素一般负责结构,而内联元素则负责文字内容,而CSS设计的初衷就是面向图文混合排版,因此内联元素在CSS世界中有着非常重要的地位。由于本章内容较多,因此特分为上下两章,依旧可以根据兴趣按小标题获取你所需要的信息。

1.字母x与CSS世界的基线

要了解内联元素,就不得不提到字母x,正是有了x,内联元素才确定了自己的基线(baseline),有了baseline之后,才有了line-height和vertical-align。因此在学习后面的内容之前,必须要了解一个概念:

字母x的下边缘线就是内联元素的基线(baseline)!

既然x那么重要,因此CSS还给了它一个特殊的称呼叫x-height。顾名思义,x-height指的就是小写字母x的高度,那么这个x-height有没有用呢?答案是肯定的,如vertical-align:middle的定义就是baseline往上1/2*x-height的位置,因此我们看到的vertical-align:middle在font-size较大的时候会显得不是特别“垂直居中”,因为不是每个字/字母的高度都和字母x相同,或者说,大部分都是不相同的。

由于vertical-align:middle需要基于x-height进行运算,那么CSS干脆给这个x-height一个名份,进一步衍生出了相对尺寸单位ex,1ex = x-height,因此这个单位会随着font属性的变化而变化,非常的不稳定,所以不太适合用来限定元素的尺寸,但某些情况下,ex的表现相当出色。如:我们需要如下效果

需要在文字后面加一个倒三角符号,这时候我们要让文字和图标对齐可能会借助vertical-align:middle,让文字和图片都基于中线对齐,事实上由于文字本身是基于baseline排版的,这时候我们只要让icon也基于baseline布局不就行了吗?那怎么让图标也基于基线布局呢?加个height:1ex不就完事了吗?如下所示:

<style>
.icon-arrow {
    display: inline-block;
    width: 20px;
    height: 1ex;
    background: url(/images/5/arrow.png) no-repeat center;
}
</style>

2.内联元素中真实存在的“行框盒子”

由于之前没有好好解析内联盒子的基础知识,这里要补充一个关于内联盒子的基础内容,这对于学习后面的知识有一定的帮助。首先我们需要明确两个非官方概念:什么是行框盒子以及行框盒子有什么用?

什么是行框盒子?事实上我并不敢说这是官方给出的一个盒模型,作者也没有提到,但他似乎真实存在于文字的每一行,而且是以行作为计算单位的,也就是每一行文字(不管他们是不是一个内联标签或匿名标签生成的),都在外面自动生成一个“行框盒子”。注意,这里仅代表我从书本上获取的观点,在后面的幽灵节点中会继续探讨官方是否给出了这个盒子。这么说可能不够直观,来看一个例子吧。

这是我用span标签生成的一段话,他被浏览器自动分割成了两行,你可以理解每行文字的外包裹就是一个行框盒子。那么什么时候会生成行框盒子呢?这里我仅凭个人测试提出两种生成行框盒子的情况,第一种,就是非空内联元素,注意这个非空,很关键,作者似乎总是忽略空元素的测试,我本人则对空元素比较在意。第二种,就是inline-block元素内部自动生成,注意这里可以为空了,具体为什么我也不知道,测试结果如此。

第二个问题是,行框盒子有什么用?作者为什么要提出行框盒子?因为这有助于我们理解很多问题。首先来看下面这个测试代码以及他们生成的结果。

<div style="background: yellow;">
    <span style="line-height: 40px">我行高40px</span>
    <span style="line-height: 60px">我行高60px</span>
    <span style="line-height: 30px">我行高30px</span>
    <span style="line-height: 90px">我行高90px</span>
</div>

此时div的角色就像是“行框盒子”,因为div包裹了一行“行框盒子”(这里只有一行,两行情况又不同了),这样做是为了让你更直观的看到最终这些内联元素最终生成的“行框盒子”,生成的高度是90px,也就是说,同一行!内联元素生成的行框盒子的高度,取决于line-height最高的那个元素。为了加深你对这个结果的印象,我再放一张图,还是刚才那几个元素,这次我把父容器div的宽度压缩了。

最终结果跟上面测试的结论相同,第一行行高最高的是60px,第二行行高最高的是90px,两个“行框盒子”一相加,得到最终的150px,对于行框盒子的印象应该够深了吧!

3.内联元素的基石line-height

明白了行框盒子的概念之后,再来看line-height,就会容易的多了。作为内联元素的基石,line-height完全掌控了内联盒子的高度,注意,我说的是完全掌控,跟font-size没有半毛钱关系。

来思考一下下面这个问题,一个空的div的高度是0,在里面写上几个字,他的高度就被撑开了,那么这个高度是由何而来呢?首先可以确定的是,是由新增的文字撑开的,在内联元素中,还有一类叫做“匿名内联元素”的近亲,他们外面没有包裹任何内联元素标签,但事实上他们的表现跟内联元素相同,只是不能单独定义自己的样式(当然可以从父级去继承一些过来,虽然继承的权重最低,但总比没有强)。好了,我们知道这几个字是匿名内联元素了,因此高度是由这个内联元素的高度撑开的,那么这个内联元素的高度又取决于什么呢?下面我们来做两个测试。

如结果所示,最终撑开高度的是line-height,而不是font-size。注意这里说的是非替换元素的纯内联元素,替换元素的概念可以看一下之前的文章。之前说过padding和border可以影响元素的高度,那么在内联元素中,是否也可以呢?答案是:不行!但有些情况下,视觉上还是有一些偏差。如下测试

<div style="background: yellow">
    <span style="line-height: 40px;padding: 20px;border: 1px solid">我有padding和border</span>
</div>

由于markdown编辑器支持标签语言,因此我们可以直接预览最终效果如下(小提示:你可以通过浏览器直接检查下面的元素看到CSS样式)

我有padding和border

因此对于纯内联元素,他的高度计算只跟line-height相关。 line-height === 纯内联元素height !!!

关于line-height的计算方式,作者“啰哩吧嗦”算了半天,我只总结成一个公式,line-height = font-size + 上半行距 + 下半行距 。对于CSS来讲,font-size是已知的,因此他只需要计算半行距即可,至于它是怎么算的,就没必要深究了。我只讲这个计算方式会导致一种情况,第一行文字和最后一行文字的上方和下方都只有半行距的距离,中间部分则是一个行距,如对样式有较高要求的可以考虑一下这个因素,用其他方式去弥补这里的高度差。

4.line-height在块级元素和内联替换元素中的疲软性

说完了line-height在内联元素中的表现,再来说说line-height在块级元素和内联替换元素中承担什么样的角色吧。

首先我们先来看简单的,line-height在块级元素中承担一个什么样的角色呢?答案是:屁用没有。当然,本章第三点的第一个例子就是在块级元素上直接写line-height属性,所以,还是有那么丁点作用的,这个作用是line-height无处不在的继承属性决定的,正好块级元素内的匿名内联元素自己写不了样式,那就只能继承父级的line-height了,所以line-height最终还是作用于内联元素,而不是块级元素。然后由内联元素生成的行框盒子相加,撑开了块级元素的高度。

既然line-height在块级元素中没用,那他在内联替换元素中又有哪些表现呢?这个等讲完vertical-align再总结。

5.深入line-height的各类属性值

本节的标题让人觉得摸不着头脑,line-height还有别的属性?还真没有,line-height就一个值,只是可以有不同的类型。

line-height的默认值是normal,注意不是none,还支持数值,百分比值和具体长度值。

首先要了解一下这个看上去就不一般的normal,normal本身是一个跟font-family有关的变量,而且在不同的浏览器中还有不同的计算值,因此对于还原设计稿的这种要求,我们不可能用默认的normal属性,同时由于line-height无处不在的继承属性,有时候不知不觉的你就把这个属性给改了,因此normal属性可以不讨论(我们一切以实用性出发)。

实际上,我们平时会设置line-height为具体的数值,如line-height:xx像素。这种做法完全适用于现在高要求的精致网站,对于需要高保真还原设计稿的需求,完全可以用用具体数值。当然也有一种情况你可以用数字或百分比,就是现在有很多网站有自己的设计标准,如微信小程序,ant-design之类的,他们会要求文章内部的行高是字号的1.3-1.4倍,文章标题的的行高是字号的多少多少倍,这种情况下你大可以根据设计要求,用比例计算方式来写行高。

这里我只推荐用长度值(1,2,3),而不是百分比值(100%,200%),你可能觉得1不是等于100%嘛,其实我也这么认为,一般都是按照喜好随便挑一个。事实上,这里面问题可大了去了。

长度值和百分比值的计算方式是完全相同的。都是font-size*长度/百分比,向下取整,14*1.4 = 19.6 = 19px,注意不是四舍五入,是向下取整的。

那么长度和百分比哪里不同呢?答案是:继承方式,就是line-height中无处不在的继承方式不同。这里我们来看一个实例:

<div class="box box-1">
    <h3>标题</h3>
    <p>内容</p>
</div>
<div class="box box-2">
    <h3>标题</h3>
    <p>内容</p>
</div>
<style>
.box   { font-size: 14px; }
.box-1 { line-height: 1.5; }
.box-2 { line-height: 150%; }
</style>

结果如下图所示:

最终表现得结果不一样,问题就在于这个继承方式。

使用数值,line-height = 本身的font-size*数值

使用百分比,line-height = 继承来的line-height*数值

你会发现,一个是基于自身的font-size,一个是基于继承的line-height,完全不同,而且数值在大多数情况下的表现更优,也更符合实际情况。所以我个人推荐不要用line-height的百分比值,就当没听过。

6.内联元素line-height的“大值特性”

其实这个问题并不能单纯的理解为,内联元素line-height取最大那么简单,其中还包含之前所说的“行框盒子”的内容,以及行框盒子产生的幽灵节点。在探索大值特性之前,我们得先了解一下,幽灵节点是什么。来看具体的实例:


<!DOCTYPE HTML>
<html>
<head>
<title></title>
</head>
<body>
<div style="background: yellow"><span style="display: inline-block;"></span></div>
</body>
</html>

注意这段代码必须要有声明。这段代码的结果是:

这里的span标签的高度确实是0,那么究竟是什么撑开了div的高度呢?就是上面提到的“幽灵空白节点”,你可以想象这个幽灵空白节点是一个没有宽度的空白节点,当然你不要去dom结构里找他,肯定是找不到的。在了解幽灵空白节点之前,先得知道,什么时候会出现这个幽灵空白节点,根据我的测试,结果跟作者的描述相同。

产生行框盒子的前面自动生成幽灵空白节点。由于之前提到了,inline-block元素内部可以生成幽灵空白节点,因此这里确实存一个一个看不见的内联元素,且他有自己的line-height。

幽灵空白节点的line-height = font-size(默认12px) * normal,因此这个幽灵空白节点就有自己的高度了,有自己的高度就撑开了inline-block的高度,inline-block又撑开了父容器的高度,那么现在就一切都说得通了。

做好了一切的准备工作,最后做个总结和检测,理解了下面这个案例,你就真正理解了内联元素的line-height。

<div class="box box1">
    <span>span: line-height:20px</span>
</div>
<div class="box box2">
    <span>span: line-height:96px</span>
</div>
<style>
.box {
    width: 280px;
    margin: 1em auto;
    outline: 1px solid #beceeb;
    background: #f0f3f9;
}
.box1 {
    line-height: 96px;
}
.box1 span {
    line-height: 20px;
}
.box2 {
    line-height: 20px;
}
.box2 span {
    line-height: 96px;
}
</style>

由于markdown编辑器支持标签语言,因此我们可以直接预览最终效果如下(小提示:你可以通过浏览器直接检查下面的元素看到CSS样式)

span: line-height:20px
span: line-height:96px

上面这两个box最终得到的高度是一样的,为什么会这样呢?

刚才说到,每个非空内联元素外面都会生出一层“行框盒子”,而每个行框盒子左边就有一个幽灵空白节点,box1>span的line-height是20px,但他隔壁住了个幽灵空白节点,继承了父元素的line-height:96px,我们已经知道行框盒子的高度是由最高的内联元素的行高决定的,因此box1的最终高度和box2的相同,都是96px。

本章内容过于多,vertical-align就放到下章讲,学了vertical-align之后我们再来看如何结合两者实现多行元素的垂直居中展示,有兴趣的可以关注后续文章,点个关注。

不忘初心,方得始终

喜欢博主的童鞋可以扫描二维码加博主好友~ 也可以扫中间二维码入驻博主的粉丝群(708637831)~当然你也可以扫描二维码打赏并直接包养帅气的博主一枚。