這是一篇譯文,對 inline 和 inline-block 的元素剖析很是給力。html
原文:Deep dive CSS: font metrics, line-height and vertical-align - Vincent De Oliveira前端
line-height 和 vertical-align 都是簡單的 CSS 屬性,以至於大多數人自覺得知道這兩個屬性的工做原理。但實際上這兩個屬性很是複雜,也許算得上是 CSS 裏最難的兩個屬性,由於這兩個屬性和 CSS 裏一個不爲人知的特性息息相關:內聯格式化上下文(IFC)(譯者注:和 BFC 相對應)。面試
舉例來講,line-height 的值能夠是一個長度(length)或者是一個數字,它的默認值是 normal。那麼,normal 是什麼呢?咱們常常將 normal 理解爲 1,或者 1.2,甚至連 CSS 規格文檔都沒有提到這一問題。咱們知道 line-height 的值爲數字時,表示的相對於 font-size 的倍數,但問題在於,font-size:100px 對應的文字在不一樣字體裏的高度是不同的!那麼 line-height 會隨着文字大小的改變而改變嗎? normal 真的表示 1 或者 1.2 嗎?vertical-align 又是如何被 line-height 影響的呢?api
讓咱們來深刻理解一個不那麼簡單的 CSS 機制。瀏覽器
下面是一段簡單的 HTML 代碼,一個 p 標籤包含了 3 個 span 標籤,每一個 span 各自有一個 font-family:bash
<p>
<span class="a">Ba</span>
<span class="b">Ba</span>
<span class="c">Ba</span>
</p>
複製代碼
p { font-size: 100px }
.a { font-family: Helvetica }
.b { font-family: Gruppo }
.c { font-family: Catamaran }
複製代碼
(譯者注:這幾款字體你的電腦上可能沒有)微信
font-size 相同,font-family 不一樣,獲得的 span 元素的高度也不一樣: app
爲何 font-size: 100px 不能獲得相同高度的元素呢?我測量了一下每一個 span 的高度:Helvetica 115px,Gruppo 97px,Catamaran 164px。 less
一款字體會定義一個 em-square,它是用來盛放字符的金屬容器。這個 em-square 通常被設定爲寬高均爲 1000 相對單位,不過也能夠是 102四、2048 相對單位。
字體度量都是基於這個相對單位設置的,包括 ascender、descender、capital height、x-height 等。注意這裏面的值是容許相對於 em-square 出血(bleed outside)的(譯者注:大概能夠理解爲超出 em-square)
在瀏覽器中,上面的 1000 相對單位會按照你須要的 font-size 縮放。
咱們把 Catamaran 字體放到 FontForge 中,分析它的字體度量:
這意味着 Catamaran 字體佔據了 1100 + 540 個相對單位,儘管它的 em-square 只有 1000 個相對單位,因此當咱們設置 font-size:100px 時,這個字體裏的文字高度是 164px。這個計算出來的高度決定了 HTML 元素的 content-area(內容區域),後面我會講到 content-area。你能夠認爲 content-area 就是 background 做用的區域。
咱們還能看出大寫字母的高度是 68px,小寫字母的高度(x-height)是 49px。因此 1ex = 49px,1em = 100px,而不是 164px。(真好,em 是基於 font-size,而不是基於計算出來的高度)
在繼續深刻以前,說點相關的知識。當 p 元素出如今屏幕上時,它可能包含了多行內容,每行內容由多個內聯元素組成(內聯標籤或者是包含文本的匿名內聯元素),每一行都叫作一個 line-box。line-box 的高度是由它全部子元素的高度計算得出的。瀏覽器會計算這一行裏每一個子元素的高度,再得出 line-box的高度(具體來講就是從子元素的最高點到最低點的高度),因此默認狀況下,一個 line-box 老是有足夠的高度來容納它的子元素。
每一個 HTML 元素實際上都是由多個 line-box 的容器,若是你知道每一個 line-box 的高度,那麼你就知道了整個元素的高度。
若是咱們修改一下最初的 HTML 代碼:
<p>
Good design will be better.
<span class="a">Ba</span>
<span class="b">Ba</span>
<span class="c">Ba</span>
We get to make a consequence.
</p>
複製代碼
那麼就會獲得 3 個 line-box(寬度固定):
咱們清楚地看到第二個 line-box比其餘兩個要高一些。由於第二行裏面的子元素由於有一個用到了 Catamaran 字體的 span。
line-box 的難點在於咱們看不見它,並且不能用 CSS 控制它。即便咱們用 ::first-line 給第一行加上背景色,咱們也看不出第一個 line-box 的高度。
目前我已經提到了兩個概念:content-area 和 line-box。若是你仔細看了,會發現我說 line-box 的高度是根據子元素的高度計算出來的,而不是子元素的 content-area 的高度。這個區別大了。 接下來講句聽起來很奇怪的話:一個內聯元素有兩個高度:content-area 高度和 virtual-area (實際區域?)高度(virtual-area 是我本身發明的單詞,它表示對人類有效的高度,你在其餘地方是看不到這個單詞的)。
這麼一來,這就打破了一個長久的謠言:line-height 表示兩個 baseline 之間的距離。在 CSS 裏,不是這樣的。
virtual-area 和 content-area 高度的差別叫作 leading。leading 的一半會被加到 content-area 頂部,另外一半會被加到底部。所以 content-area 老是處於 virtual-area 的中間。
計算出來的 line-height(也就是 virtual-area 的高度)能夠等於、大於或小於 content-area。若是 virtual-area 小於 content-area,那麼 leading 就是負的,所以 line-box 看起來就比內容還矮了。
還有一些其餘種類的內聯元素:
這類內聯元素,其高度是基於 height、margin 和 border 屬性(譯者注:好像漏了 padding)。若是你將其 height 設置爲 auto 的話,那麼其高度的取值就是 line-height,其 content-area 的取值也是 line-height。
咱們目前依然沒有解釋 line-height:normal 是什麼意思。要解答這個問題,咱們又得回到 content-area 高度的計算了,問題的答案就在字體度量裏面。
咱們回到 FontForge,Catamaran 的 em-square 高度是 1000,同時咱們還看到不少其餘的 ascender/descender 值:
在 Catamaran 這款字體中,Line Gap 的值是 0,那麼 line-height: normal 的結果就跟 content-area 的高度同樣,是 1640 相對單位。
爲了對比,咱們再看看 Arial 字體,它的 em-square 是 2048,ascender 是 1854,descender 是 434,line gap 是 67。那麼當 font-size: 100px 時,
全部這些值都是由字體設計師設置的。
這麼看來,line-height:1 就是一個很糟糕的實踐。記得嗎,當 line-height 的值是一個數字時,其實就是相對 font-size 的倍數,而不是相對於 content-area。因此 line-height:1 頗有可能使得 virtual-area 比 content-area 矮,從而引起不少其餘的問題。
不只僅是 line-height:1 有問題,我電腦上的 1117 款字體中,大概有 1059 款字體的 line-height 比 1 大,最低的是 0.618,最高的是 3.378。你沒看錯,是 3.378!
line-box 計算的一些細節:
我還沒提過 vertical-align 屬性,它也是計算 line-box 高度的重要因素之一。咱們甚至能夠說 vertical-align 是內聯格式化上下文(IFC)中最重要的屬性。
它的默認值是 baseline。還記得字體度量裏的 ascender 和 descender 嗎?這兩個值決定了 baseline 的位置。不多有
字體的 ascender 和 descender 的比例是一比一的,因此咱們常常看到一些意想不到的現象,下面是例子。
代碼以下:
<p>
<span>Ba</span>
<span>Ba</span>
</p>
複製代碼
p {
font-family: Catamaran;
font-size: 100px;
line-height: 200px;
}
複製代碼
一個 p 標籤內有兩個 span 標籤,span 繼承了 font-family、font-size 和 200px 的 line-height。這時兩個 span 的 baseline 是等高的,line-box 的高度就是 span 的 line-height。
若是第二個 span 的 font-size 變小了呢?
span:last-child {
font-size: 50px;
}
複製代碼
咱們會發現一個很是奇怪的現象,line-box 的高度變高了!以下圖所示。提示你一下,line-box 的高度是從子元素的最高點到最低點的舉例。
這個例子能夠做爲「應該將 line-height 的值寫成數字」的論據,可是有時候咱們爲了作出好看的排版,必須把 line-height 寫成一個固定值。
不過我實話告訴你吧,無論你把 line-height
寫成什麼,你都會在對齊內聯元素的時候遇到麻煩。 咱們來看另外一個例子。p 標籤有 line-height:200px,內含一個 span,span 繼承了 p 的 line-height。
<p>
<span>Ba</span>
</p>
複製代碼
p {
line-height: 200px;
}
span {
font-family: Catamaran;
font-size: 100px;
}
複製代碼
此時 line-box 的高度是多少?貌似是 200px,但其實不是。這裏你沒有考慮到的問題是 p 有本身的 font-family,默認值是 serif。p 的 baseline 和 span 的 baseline 位置不同,所以最終的 line-box 比咱們預想的要高一些。出現這種問題是由於瀏覽器認爲每一個 line-box 的起始位置都有一個寬度爲 0 的字符(CSS 文檔將其稱爲 strut),並將其歸入 line-box 的高度的計算中。
看不見的字符,看得見的影響。
爲了說明這個問題,咱們畫圖解釋一下這個問題。
用 baseline 來對齊使人費解,若是咱們用 vertical-align: middle 會不會好一點呢?讀 CSS 文檔你會發現,middle 的意思是「用父元素 baseline 高度加上父元素中 x-height 的一半的高度來對齊當前元素的垂直方向的中點」。baseline 所處的高度跟字體有關,x-height 的高度也跟字體有關,因此 middle 對齊也不靠譜。更糟糕的是,通常來講,middle 根本就不是居中對齊!內聯元素的對齊受太多因素影響,所以不可能用 CSS 實現。
順便一說,vertical-align 的其餘 4 個值有可能有點用:
不過你依然要當心,大部分狀況下,對齊的是 virtual-area,也就是一個不可見的高度。看看下面這個用 vertical-align:top 的例子:
最後,vertical-align 的值也能夠是數字,表示根據 baseline 升高或下降,不到萬不得已仍是別用數字吧。
咱們討論了 line-height 和 vertical-align 若是互相影響,如今問題來了:CSS 能夠控制字體度量嗎?簡單來講答案是:不行。我也很想用 CSS 來控制字體。不管怎樣,我仍是想試試。字體度量只是一些固定的值而已,咱們應該能夠圍繞它作點什麼。
好比說,咱們想要一段文字使用 Catamaran 字體,同時大寫字母的高度正好是 100px,看起來能夠實現,咱們只須要一些數學知識。
首先咱們把全部字體度量設置爲 CSS 自定義屬性,而後計算出一個 font-size,讓大寫字母的高度正好是 100px。
p {
/* font metrics */
--font: Catamaran;
--fm-capitalHeight: 0.68;
--fm-descender: 0.54;
--fm-ascender: 1.1;
--fm-linegap: 0;
/* desired font-size for capital height */
--capital-height: 100;
/* apply font-family */
font-family: var(--font);
/* compute font-size to get capital height equal desired font-size */
--computedFontSize: (var(--capital-height) / var(--fm-capitalHeight));
font-size: calc(var(--computedFontSize) * 1px);
}
複製代碼
看起來也並不複雜不是嗎?若是咱們想要文字垂直居中怎麼辦呢?也就是讓 B 上面的空間和下面的空間高度同樣。爲了作到這一點,咱們必需要根據 ascender 和 descender 的比例來計算 vertical-align。
首先計算出 line-height:normal 的值和 content-area 的高度:
p {
…
--lineheightNormal: (var(--fm-ascender) + var(--fm-descender) + var(--fm-linegap));
--contentArea: (var(--lineheightNormal) * var(--computedFontSize));
}
複製代碼
而後咱們須要計算:
像這樣:
p {
…
--distanceBottom: (var(--fm-descender));
--distanceTop: (var(--fm-ascender) - var(--fm-capitalHeight));
}
複製代碼
而後咱們就能夠計算 vertical-align 的值。
p {
…
--valign: ((var(--distanceBottom) - var(--distanceTop)) * var(--computedFontSize));
}
span {
vertical-align: calc(var(--valign) * -1px);
}
複製代碼
最後,設置 line-height:
p {
…
/* desired line-height */
--line-height: 3;
line-height: calc(((var(--line-height) * var(--capital-height)) - var(--valign)) * 1px);
}
複製代碼
添加一個和 B 同樣高的 icon 就很容易了:
span::before {
content: '';
display: inline-block;
width: calc(1px * var(--capital-height));
height: calc(1px * var(--capital-height));
margin-right: 10px;
background: url('https://cdn.pbrd.co/images/yBAKn5bbv.png');
background-size: cover;
}
複製代碼
注意這只是爲了演示,請不要在生產環境中使用此方案。
咱們知道了:
可是我依然喜歡 CSS :)
加飢人谷微信號進入前端技術交流羣: astak10 ,暗號:寫代碼啦
每日一題,每週資源推薦,精彩博客推薦,工做、筆試、面試經驗交流解答,免費直播課,羣友輕分享... ,數不盡的福利免費送