由于一次内联元素错位引发的line-height思考

270 阅读8分钟

line-height对于一个前端小可爱来说,应该是一个会经常碰面的老朋友了。可是有一天,我突然发现自己好像对他没那么了解,他也没有外表看起来的那么简单。

事情的经过是这样的……

在偶然一次工作中,我写了这样的模板:

<div>
    <span class="name">重大疾病险</span>
    <span class="tip">保额每月可累计</span>
</div>
div{
    font-family: "PingFang SC";
}
.name{
    line-height: 20px;
    font-size: 20px;
}
.tip{
    display: inline-block;
    line-height: 20px;
    font-size: 14px;
}

两个相邻的内联元素,字体一大一小,行高相同,由于第二段文字需要有字多情况就自动去下一行的效果,所以第二个spaninline-block我畅想的结果是,两个span高度都是20px,div高度也是20px,多么完美。但结果往往不近人意……


div的高度怎么是28??

再一看子元素,一个28一个20??

疑问1:难道是line-height对行内元素不生效??
  然而规范告诉我,对于非替代的inline元素,它用于计算行盒(line box)的高度。

疑问2:既然生效了,为什么审查元素看着是28,不是20呢?
  对于文本来说,存在一个内容区域(content area)。你可以理解为,用光标选中这行文字时带背景的区域。他同时受font-family和font-size的影响。就算是相同的字号,如果字体不同,‘你所看到’的高度也是不一样的,同学们可以自己尝试一下。这里强调你所看到的,也就是我们这里的28px,它其实是这个文本内容区的高度,而内容区的高度并不是真正的高度,也就是说,它不会影响这个元素真正的尺寸,也不会撑起父元素的高度,所以只是一个你看起来的高度。而对于行内元素来说,真正影响它高度的就是line-height
  在这里,我们可以通过改变他们的font-family来证实一下。

div{
    font-family: "HelveticaNeue";
}
.name{
    line-height: 20px;
    font-size: 20px;
}
.tip{
    display: inline-block;
    line-height: 20px;
    font-size: 14px;
}

可以看到,更改了字体之后,.name的高度由28变成了24,.tip由于是inline-block所以高度没变,但是父div的高度依然是28px,说明content area并不会影响父元素的真实高度。

疑问3:那如何看到它真正的高度?
  你可能也发现了,第二个span是inline-block,它的高度就是20px,那我们将第一个span也设置成inline-block来看一下。

果然,它的高度终于变成了我们期望的20px。
那我们再来看一下父元素div的高度:

!!怎么还是28px??

疑问4:两个子元素都已经是20px了,为什么父元素还是28px?
这里先声明两个问题:
(1)证明前面疑问2中说的没错,content area是不会影响实际尺寸的,现在我们元素都是20px了,可父元素依然还是28px。
(2)我们将第一个span设置成inline-block之后,可以看到它的尺寸已经变成了20px,但这并不代表这它就没有content area了,或者它的content area变成了20px。你可以理解为我们给他外面包了一层盒子,你可以看到盒子的真正的尺寸,而content area在它里面,你只是看不到罢了。更何况,它是一个影响不了谁的东西。

接着回到我们的问题,仔细看上图,可以发现,两个高度都是20px的子元素,上下都有一定的间隙;再仔细看,发现它俩没对齐,红色背景的靠上,粉色背景的靠下,它俩之间稍微有些错位。就是这些,让我们的父元素被撑成了28px。那它们又是从哪里来的呢?

既然是对不齐导致父元素被撑高了,那css中相关对齐的属性,我们很容易想到是vertical-align,难道是它搞得鬼?那我们试着改一下它们的vertical-align

vertical-align:top,上面对不齐了

vertical-align:middle,好像跟之前差别不大……

虽说这些属性值都没能让他们对齐,但可以发现确实是与它有关的,可能是我们哪里使用不当,影响了这个属性?那我们就去了解一下vertical-align是怎么对齐的,起初我们不设置的时候,会取它的默认值:baseline—— 基线对齐。
内联元素默认是基线对齐的,而基线就是指行框盒子中字母'x' 的下边缘:

诶??再仔细看看我们之前对不齐的那张图,你会发现,虽然元素没对齐,但是文字下端是对齐的,这不就是正常的基线对齐吗!这下就说的通了。

划重点!!
行内元素的对齐方式默认是基线对齐,也就是都与 x 的下边缘对齐。这两个span的高度是一样的,都是20px,然而重点就在于它们的字体大小不一样。而文本在如果设置了line-height,是会基于line-height居中的,也就是说,它们分别在各自高度相同框框中居中,但由于字号大小不一样,所以文字底部是对不齐的,但他们又需要基线对齐,所以它俩开就必须要错位一下。
这里引用一下鑫旭大神的图:

疑问5:那这是不是就说明,我们把两个元素的行高设置成不一样,就可以不错位把父元素撑高了?

这里我们分别给两个span设置与自己字体大小一样的行高:20px和14px,可以看到它俩已经没有向外错位了,但是父元素的高度并没有如期,还是28px。怎么回事?难道还有别的东西再影响它?

你猜对了,但这是个我们看不见的东西。引用一下张鑫旭大神的叫法——幽灵空白节点。
“幽灵空白节点”是内联盒模型中非常重要的一个概念,具体指的是:在 HTML5 文档声明 中,内联元素的所有解析和渲染表现就如同每个行框盒子的前面有一个“空白节点”一样。这 个“空白节点”永远透明,不占据任何宽度,看不见也无法通过脚本获取,就好像幽灵一样, 但又确确实实地存在,表现如同文本节点一样,因此,我称之为“幽灵空白节点”。

既然看不见,那我们可以拿一个看的见的 x 字符来假装它。

<div>
    <em>x</em>
    <span class="name">重大疾病险</span>
    <span class="tip">保额每月可累计</span>
</div>
div{
    font-family: "PingFang SC";
}
em{
    display:inline-block; //为了审查元素看到的不是content area
    font-style:normal;
}
.name{
    line-height: 20px;
    font-size: 20px;
}
.tip{
    display: inline-block;
    line-height: 20px;
    font-size: 14px;
}

这个x字号和行高都没有设置,字号是继承下来的20px,元素行高默认是1.x(1.x倍的font-size),具体数值还有待研究,但绝对是>1的,由图可见,什么都没有设置的情况下,这个x的高度就是28px,也就代表着,那个幽灵空白节点的高度就是28px。
那我们将幽灵空白节点的行高设小一些不就好了?可是这个幽灵节点,看不见摸不着,怎么设啊。别忘了,line-heigt是可继承性的,我们给父div设置line-heigt,幽灵节点自然就会继承。

解决一
我们可以给父divline-height设置一个很小的值

div{
    line-height: 5px;
    font-family: "PingFang SC";
}
em{
    display:inline-block; //为了审查元素看到的不是content area
    font-style:normal;
}
.name{
    line-height: 20px;
    font-size: 20px;
}
.tip{
    display: inline-block;
    line-height: 20px;
    font-size: 14px;
}

这样,幽灵节点的行高就只有5px,父元素行高20px,没毛病。但这个方法还不够完美,因为你需要想一下这个很小的值……

解决二
当line-height设置为一个无单位的数值时,表示是某倍的font-size。

div{
    line-height: 1;
    font-family: "PingFang SC";
}
em{
    display:inline-block; //为了审查元素看到的不是content area
    font-style:normal;
}
.name{
    line-height: 20px;
    font-size: 20px;
}
.tip{
    display: inline-block;
    line-height: 20px;
    font-size: 14px;
}

给父元素设置line-height为1,这样,子元素高度都是自己的font-size,包括幽灵空白节点。这样就不会再有人无意间撑高父元素了。当然,除非你的幽灵空白节点继承下来了一个很大的font-size,这点你得明白。

解决三
既然行高可以与font-size有关,font-size也是可继承的,那我们将父元素的font-size设为0

div{
    font-size: 0px;
    font-family: "PingFang SC";
}
em{
    display:inline-block; //为了审查元素看到的不是content area
    font-style:normal;
}
.name{
    line-height: 20px;
    font-size: 20px;
}
.tip{
    display: inline-block;
    line-height: 20px;
    font-size: 14px;
}

可以看到,我们模仿的那个幽灵节点已经没有了,宽高都是0了,父元素的高度也自然就回到了20px。

总结
以上,可以说是我对行内元素对齐问题的一些思考总结吧。如果你还有疑问,可以再多看几遍,自己尝试一下再继续思考思考。这个问题我也是来来回回想了好多遍,也欢迎大家评论提问,我们可以一起讨论~
前端小白,有讲解错误或不全之处,还望大佬们嘴下留情,欢迎指正~