上一篇中提到自己 debug 時遇到一個 css 知識盲點:行高和字體的關系。隨后查了相關資料,在這里梳理下這個知識,最后解釋下產生這個現(xiàn)象的原因。
現(xiàn)象
一般我們要讓文字在容器中產生垂直居中的效果,會把容器高度和行高設置成一樣,來達到這個效果(代碼,效果圖如下)。
<div style="height:32px; line-height:32px; border:1px solid black;">
<span>Test<span>
</div>
但是當容器的字體大小和文字的字體大小不一致的時候,字體就無法居中了(代碼,效果圖如下)。
<div style="height:32px; line-height:32px; border:1px solid black; font-size:42px">
<span style="font-size:20px;">Test<span>
</div>
原理概述:行內元素如何排版
在解釋上述現(xiàn)象之前,我們先來看下行內元素(inline-element)在頁面上是如何排版的。我們先假設頁面上沒有任何我們添加的 css,每個行內元素會占據一定的矩形空間,我們稱之為行內框(inline-box),如下圖和代碼:
<div style="border:1px solid black; color:white;">
<span>span</span> <em>em</em> <strong>I am very very very very very very strong</strong>
</div>
span
em
strong
都是行內元素,背景顏色標出了它們行內框所占的空間(這里只是為了圖示方便,用顏色標示了行內框占據的大致空間,因為 css 中并沒有給行內元素的行內框設置顏色的屬性)。而如果沒有用行內元素包裹的文字呢?瀏覽器會給文字生成一個匿名的行內框,如下圖ih-pic3.png 中用綠色框框出來的所示。
<div style="border:1px solid black; color:gray;">
anonymous <span>span</span> <em>em</em>
</div>
上面兩個例子字體都是統(tǒng)一大小的,如果每個行內元素字體不一樣大小呢?每個行內框會根據各自的 vertical-algin
在垂直方向上對齊,我們先看下示例(示例中每個行內元素的 vertical-algin
都設置成一樣,但是每個元素的 vertical-algin
也可以各自不同,這里我就省略了各自不同的例子):
<style>
span { font-size: 32px; }
em { font-size: 20px; }
strong { font-size: 16px; }
.vertical-align-wrapper * {
vertical-align: top; /* 對應的是圖ih-pic5.png */
/* vertical-align: middle; 對應的是圖ih-pic6.png*/
/* vertical-align: baseline; 對應的是圖ih-pic7.png*/
/* vertical-align: bottom; 對應的是圖ih-pic8.png*/
}
</style>
<div class="vertical-align-wrapper" style="border:1px solid black; color:gray;">
anonymous <span>span</span> <em>em</em> <strong>I am very very very very very very strong</strong>
</div>
如果一行中的行內元素字體大小和 verticial-align
各不相同,那么整個一行占據空間是如何算的呢?這一行的高度最高處由垂直方向上行內框最高的元素決定,最低處由垂直方向上行內框最低的元素決定(代碼,圖如下):
<style>
span { font-size: 30px; vertical-align: top; }
em { font-size: 30px; vertical-align: top; }
strong { font-size: 30px; vertical-align: top; }
</style>
<div style="border:1px solid black; color:gray; vertical-align: bottom;">
anonymous <span>span</span> <em>em</em> <strong>I am very very strong</strong>
</div>
圖ih-pic9.png 中黃色框表示的就是整行占據的空間,我們稱之為行框(line-box)。再給一個帶上下標的示例看看是如何計算行框占據的空間(還是黃色框占據的空間表示行框):
<div style="border:1px solid black; color:gray; vertical-align: bottom;">
anonymous <span>span</span><sup>sup</sup> <em>em</em><sub>sub</sub> <strong>I am very very strong</strong>
</div>
所以行內元素字體大小決定了字的大小,每個行內元素占據的空間又由行內框(inline-box)決定,最后一整行如果只有行內元素,則所占據的空間就是行框(line-box)決定的,高度亦是行框的高度。如果文字很長折成了多行,那么每行都是按照以上規(guī)則生成多個行框,并在垂直方向上以容器內部邊界的左上角開始向下排列:
接下來我們就要看下 line-height 是怎么影響元素的了。剛剛我們提到一個行內元素占據的空間叫行內框,那么行內框的高度是如何決定的呢?就是 css 屬性行高(line-height
)決定,瀏覽器通常全局的默認行高是 normal,約等于字體的1.2倍。譬如一個行內元素的字體是 12px,那么行高就是 12 x 1.2 = 14.4(px)。而行高和字體間的距離差距我們叫做 leading(中文可以叫做行間距,不過網頁上的行間距和傳統(tǒng)印刷業(yè)的行間距代表的地方不同,這里就不描述其歷史和不一樣的地方了)。瀏覽器會把 leading 除以二,平均放到字體的上面和下面,這兩塊平均空間的距離叫做 half-leading,如下圖(圖中為了演示明顯,字體大小設為 16px,行高設為 32px):
如果 line-height 小于字體大小,那么行內框的高度就會向字體的中心線收縮,字體將會溢出行高,如下圖(圖中為了演示明顯,字體大小設為 32px,行高設為 20px):
行內框的高度會影響行框的高度,我們假設極端情況每行只有一個行內元素,那么每行行框(line-box)的高度就是這個元素行內框(inline-box)的高度,讓我們看看多行情況下行高(line-height)大于字體和小于字體產生的效果,溢出行高的字體將在多行垂直方向疊加:
最后我們說下如果手工設置了元素的 line-height,將如何影響元素:
- 行內元素設置 line-height 等于指定了行內框的高度。
- 沒有行內元素包裹的文字所生成的匿名行內框的高度是繼承自它的父容器。
- 塊級元素包裹了一系列行內元素,這些行內元素自己沒有設置 line-height,那么給塊級元素設置 line-height 等于給被包裹的行內元素指定了最小行內框的高度。
現(xiàn)象產生原因分析
一般情況:
<div style="height:32px; line-height:32px; border:1px solid black;">
<span>Test<span>
</div>
div 設置的 line-height:32px 給 span 限定了行內框的最小高度,而 span 的默認行高和字體都沒有超過 32px,所以 span 的行內框高度就是 32px,又因為 half-leading 平均分配的原理,那么字體就會在行內框中居中,如果一行字行內框高度相同,那么整行文字的行框高度就是 32px,最后 div 的高度又設置了 32px,所以最后文字看起來是垂直居中的。(其實如果以此例來說 div 不設置高度也沒有問題,因為 div 高度就是按內部元素高度而撐開的)
異常情況:
<div style="height:32px; line-height:32px; border:1px solid black; font-size:42px">
<span style="font-size:20px;">Test<span>
</div>
雖然看上去 div 只有一個子元素 span,但是從規(guī)范上我得知,這種情況下有一個寬度無限小的匿名行內框存在。如果在 div 上設置了字體 42px,那么這個匿名行內框的字體就變成了 42px,且 div 的 line-height 只能指定匿名行內框的最小高度,當匿名行內框的字體大于 32px 時,這個匿名行內框的高度就變大超過了 32px,最后導致整行的行框高度超過 32px。而 span 的行內框和字體雖然都沒有超過 32px ,但是它的 baseline 需要在水平方下沉和這個匿名行內框對齊,但是 div 的高度限制成了 32px,最后看上去 span 就不在垂直居中的方向上了。