本文依然是对《CSS世界》这本书中的一些知识点进行探究和实践。内联元素在CSS中极为重要,块级元素负责结构,内联元素负责内容,而CSS又是为图文展示而设计,因此显得尤为重要。同时多个属性混合在一起共同作用又会产生较为复杂的结果,比较典型的就是line-heightvertical-align,以及“幽灵空白”(CSS文档中叫strut)产生的一些现象了。

文中也将从这三个方面去入手,实践一些现象产生的原因。

幽灵空白

“幽灵空白”这个概念并非没有意义,内联元素中产生的很多看似很难解释的现象其实很多都和这个“幽灵空白”息息相关。

证明幽灵空白存在

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.”

上面是一段对strut的描述,书中将其称为幽灵空白,表现为看不见摸不着,但却真实存在,可以用一段代码证明其存在。

1
2
3
<div>
<span></span>
</div>

css代码如下

1
2
3
4
5
6
7
div {
background-color: #000000;
}

span {
display: inline-block;
}

从上图可以看出来,其中并没有内容,内部的span宽高也都是0,但是div的高度却并不为0,而是20.8px,即可以认为span元素前面还有一个宽度为0的空白字符,那么都可以解释通了。

Line boxes that contain no text, no preserved white space, no inline elements with non-zero margins, padding, or borders, and no other in-flow content (such as images, inline blocks or inline tables), and do not end with a preserved newline must be treated as zero-height line boxes

同样还要注意,这里display要指定为inline-block,否则按照上述文档的描述,高度会被视为0

这里为什么是20.8px呢?根据我的理解,这里的20.8px即内容区域(content area),即内联盒模型中的一个不可见区域。根据《css世界》作者的理解与实践,可以将其理解为文本选中背景色区域。

这里内容区域是由font-familyfont-size共同决定的,可以做一个测试,比较设置font-family: simsun和不设置时的区别。(默认为微软雅黑)

1
2
3
4
div {
background-color: #000000;
font-family: simsun;
}

设置之后,高度变为18.4px

即内容区域的高度和字体相关。当然这里还是有一个疑惑,设置宋体之后,内容区域应该和em-box相同,按照默认16px,高度应该是16px而非18.4px。这里暂未找到原因。

line-height

line-height属性可以说是内联元素的基石

决定非替换元素的高度

决定高度要分成两部分来讲,对于替换元素和非替换元素来讲是不同的

非替换元素

对于非替换元素(替换元素比如img)的内联元素来将,其高度是由line-height决定的。比如说一个<div></div>是高度为0,当写上几个字之后就有了高度,但是这里的高度并非由font-size决定,而是由line-height决定的。可以用代码测试一下。

1
2
3
4
5
6
<div>
<div class="test1">高度</div>
</div>
<div style="margin-top: 20px">
<div class="test2">高度</div>
</div>

css代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
.test1 {
font-size: 16px;
line-height: 0;
border: 1px solid #ccc;
background-color: #eee;
}

.test2 {
font-size: 0;
line-height: 16px;
border: 1px solid #ccc;
background-color: #eee;
}

可以看到,上面font-size为16px的只有border撑起来的2px,而下面的line-height为16px的则是一共18px的高度。由此可以得出结论,内联元素的高度是由line-height来决定的。

替换元素

对于替换元素来讲,比如说图片,设置其容器的line-height并不会影响到图片,但是由于图片为内联元素,前面也然会有一个幽灵空白,所以会造成容器被撑大的现象,但并未影响到图片。

只有在非替换元素下,才能决定高度,在混合情况下,比如说图文混排时,则只能决定最小高度,不仅是替换元素不受line-height影响,同时也有vertical-align的影响。

行距

行距是内容排版上很重要的一点,但是由于平时开发中,也都是看着差不多就行了,并没有对其中的实现有所了解。这里也通过行距来进一步理解line-height在内联元素中重要的作用。

行距在css里是分成上半部分和下半部分的,即第一行文字上面也是会有一半行距的。行距的计算公式即行距 = 行高 - em-box,简单讲就是行距 = line-height - font-size

这里要注意一点,内容区域和em-box不一样,内容区域受font-familyfont-size共同决定的,而em-box只受font-size决定,所以你会发现,设置不同的字体,同样的font-size,按照公式计算的话行距是一样的,但是肉眼看到的行距似乎却并不一样。

但是有一个字体例外,宋体的内容区域和em-box的区域是等同的。

1
2
3
<div class="test">
<span>sphinx</span>
</div>

css代码如下

1
2
3
4
5
6
7
8
9
.test {
font-family: simsun;
font-size: 80px;
line-height: 120px;
background-color: yellow;
}
.test > span {
background-color: white;
}

行距即上下两边黄色的部分,这里可以做一个测试,将字体改为微软雅黑之后

可以明显的看到,内容区域的高度并不一致。

内联元素垂直居中

单行文字

这里要纠正一个之前一直犯的错误,单行文字垂直居中只需要line-height即可,并不需要画蛇添足加一个height

举个例子,一个高度为50px的标题垂直居中

1
2
3
4
5
6
.title{
/* height: 50px; */
line-height: 50px;
background: #000;
color: #fff;
}

并不需要注释掉的那一行

多行文字或替换元素

这里需要line-heightvertical-align共同作用

1
2
3
<div class="box">
<div class="content">基于行高实现的多行文字垂直居中效果,垂直居中效果</div>
</div>

css代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
.box {
width: 280px;
line-height: 120px;
background-color: #f0f3f9;
margin: auto;
}
.content {
display: inline-block;
line-height: 20px;
margin: 0 20px;
text-align: left;
vertical-align: middle;
}

这里需要解释一下原理

  • box里的width和margin共同作用外部容器水平居中
  • box里的line-height决定容器高度
  • content里的display很关键
    1 这里重置了外部line-height大小,不能指定为block等块级元素,需要保持内联元素特性,因为需要使用vertical-align属性
    2 同时产生行框盒子及幽灵空白。这里需要幽灵空白被box的line-height作用,撑开外部容器。
  • vertical-align: middle是为了调整对其方式,默认向基线对齐,需将其改成近似居中对齐

line-height的大值特性

大值特性,其实内在原因同样是由于幽灵空白的作用。举个例子

1
2
3
4
5
6
<div class="box box1">
<span>span: line-height:30px</span>
</div>
<div class="box box2">
<span>span: line-height:96px</span>
</div>

css代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.box {
width: 280px;
margin: 1em auto;
outline: 1px solid #beceeb;
background: #f0f3f9;
}
.box1 {
line-height: 96px;
}
.box1 span {
/* display: inline-block; */
background-color: #ddd;
line-height: 30px;
}
.box2 {
line-height: 30px;
}
.box2 span {
/* display: inline-block; */
background-color: #ddd;
line-height: 96px;
}

可以看到box容器高度都是96px,那么解释一下原因,还是由于幽灵空白,span元素前面可以假设把它加上一个x匿名内联元素。

加入是外部设置line-height为96px,那么则是作用到了x这个幽灵空白上,倘若是子元素设置了line-height,则是作用到了子元素的行框盒子上。整体行框盒子由最大的决定,因此还是96px。

如果想避免幽灵空白的干扰,将上面注释掉的代码恢复,即设置inline-block创建一个独立的行框盒子,就能将里面的子元素不受干扰。同时满足大值特性

vertical-align

说到这个属性,就要提到基线,这也是很多现象产生的原因,比如说vertical-align: middle其实是近似垂直居中。

而这近似垂直居中又导致了很多现象的出现,比如说很常见的一个布局,一行字加上一个下拉符号

设置的都是20px大小,而且也使用了vertical-align: middle,但是最后的结果整个容器总是要大于20px,原因就和基线的定义有关了,如下图所示

那么基线的定义就是字母x的下边缘,middle则是指基线往上1/2的x-height的位置,可以理解为x中间的那个交点。看似没有什么问题,但实际上问题就由此产生,每个字体在行内盒子的位置是不一样的,上面也比较了微软雅黑和宋体两个字体的差异,可以明显发现微软雅黑字体会下沉,那么也就导致了middle和实际的中线位置会往下偏一点。那么这个偏一点具体偏多少由font-size的大小决定,越大则下沉的效果越明显。所以实现真正意义上的居中是很困难的。

拿一个作者博客的例子

可以很明显的看到,确实如我上文所说,要想实现真正意义上的垂直居中,可以设置font-size为0,这样x就缩小为一个点,就能实现垂直居中效果,当然开发中并非一定要真正实现这种,一般情况下也看不出来

单行文本高度不等于行高现象

1
2
3
<div>
<span>123</span>
</div>

css代码如下

1
2
3
4
5
6
7
8
9
div {
background-color: #000;
line-height: 32px;
}

span {
color: #fff;
font-size: 24px;
}

可以看到如图中所示,高度并不为32px,出现这一现象也是由于vertical-align以及幽灵空白作用的结果,理解这一现象本质,有助于理解内联元素。

这似乎与上面行高决定非替换元素的高度这一观点相矛盾,其实并非如此,很多现象都是很多属性共同作用的结果。这里产生的原因就是子元素设置了font-size属性,导致和父元素字体大小不同,而内联元素又是和基线对齐,导致错位。

我们可以在span前面加上一个x作为匿名内联盒子,在span里面加上一个x作为内联盒子

可以看到,本来匿名内联盒子的x位置好好的,但是由于内联盒子字体太大了,导致为了基线对其,而将文字上移,就把父级容器撑开了,导致高度大于32px也就是这个原因。

解决方案很简单,原因是font-size产生的,自然可以从字体大小上改动,指定父元素的font-size为24px与子元素相同,就能使高度为32px

图片底部留有间隙现象

这个现象可以说是从我刚开始学css起就遇到过了,结果之一也就不了了之了,只知如何解决,却不理解产生这种现象的原因。

1
2
3
<div class="box">
  <img src="1.png">
</div>

css代码如下

1
2
3
4
5
6
7
8
9
.box {
width: 280px;
outline: 1px solid #aaa;
text-align: center;
}

.box > img {
height: 96px;
}

可以看到,按照代码来看,应该是容器被图片撑开,然后是96px才对,然而实际确实100px,产生间隙的原因很多情况下都是line-heightvertical-align幽灵空白造成的,此处也不例外

这里和前面一样加上x,并添加上背景色辅助观察。可以很明显地看到产生间隙的原因,内联元素中,图片的基线按照下边缘来决定。

因此,图片下边缘和x下边缘对齐,那么下边的间隙就是半行距,没错,间隙就是半行距撑开的。既然知道了产生的原因,那么解决起来就很简单了,消除半行距即可。比如让父元素line-height足够小,另行距为0或者是负值,即可解决。当然,也可以直接消除幽灵空白,将图片块状化,幽灵空白就消失了。或者改变对齐方式也可以解决。

总结

css看似简单但也确实是有很多值得去探讨的地方,理解内在的原理也许才能更好的设计出更加合理的布局。