深入了解CSS字体度量,行高和vertical-align
本文英文出處:http: //iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align 著作權歸作者所有。
轉載自https: //www.w3cplus.com/css/css-font-metrics-line-height-and-vertical-align.html
line-height和vertical-align在CSS中是兩個簡單的屬性。如此簡單,大多數人都相信自己已經完全理解它們是如何工作的以及如何使用它們。但事實上并不如此。他們其實很復雜,也是CSS中難點之一,而且也是CSS中特性之一:內聯格式化上下文(inline formatting context)。
比如可以設置line-height帶有長度單位的值或一個無單位的值,但其默認值是normal。那么在CSS中normal是什么呢?我們常常認為它是(或者應該是)1或者1.2,甚至也可以說,CSS規范都不清楚是哪一個。我們也知道,沒有單位的line-height是相對于font-size的,但問題是,font-size: 100px;在使用不同的字體(font-family)表現的行為是不一樣的,所以line-height總是相同或不同的嗎?真的是1還是1.2嗎?另外vertical-align對line-height的影響又是什么呢?
要深入研究CSS的機制可以說沒有這么簡單……
首先來聊font-size
首先來看一個簡單的HTML代碼,一個<p>標簽中包含了三個<span>標簽,每個<span>都使用不同的font-family:
<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,但渲染出來的line-height是不同的:
即使我們意識到這種行為,但還是不清楚為什么font-size:100px時元素的height不是100px?我測量發現:Helvetica字體的高度是115px,Gruppo字體的高度是97px和Catamaran字體的高度是164px。
起初似乎有點奇怪,但它是完全可預期的。這主要還是font-family的原因。那就要搞清楚它是如何工作的:
- 字體定義其em-square,每個字符將會繪制出自己的容器。這個正方形使用相對單位和生成一個1000單位。但它也可以是1024,2048或者其他
- 根據其薦對單位,字體的度量可以根據一些設置(ascender,descender,capital height,x-height等)來決定。注意,有些值是em-square之外的值
- 在瀏覽器中,相對單位是用于縮放用來適應所需的font-size
讓我們來看Catamaran字體,并且在FontForge中來看這個字體的度量參數:
- em-square是1000
- 上升(ascender)是1100和下降(descender)是540。相同的測試下,瀏覽器使用HHead Ascent/Descent值(Mac)和Win Ascent/Descent值(Windows),這些值可能不同。我們還需要注意,Capital高度是640和x-height的值是485
這意味著Catamaran字體在1000個單位的em-square使用了1100 + 540個單位,也就是說font-size:100px的時候,其高度是164px。這個計算高度定義了元素內容高度(在這篇文章中其它部分引用這個術語content-area)。你可能想到是內容區域相當于background屬性。
我們也可以預測,大寫字母是68px高度(680個單位)和小寫字母(x-hegiht)是49px高度(485個單位)。因此,1ex = 49px和1em = 100px,而不是164px(值得慶幸的是,em是基于font-size計算,而不是height)。
在繼續深入之前,先要了解這涉及到什么?當<p>元素呈現在屏幕上,它根據它的寬度可以有很多線。每一行是由一個或多個行內元素(HTML標簽元素或匿名內聯元素文本內容)組成,專業術語稱為行盒(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>它會生成三個line-box:
- 第一個和最后一個每個包含一個匿名內聯元素(文本內容)
- 第二個包含了兩個匿名內聯元素和三個<span>
<p>元素(黑色邊框)產生了一個line-box(白色邊框),其包含了內聯元素(實心邊框)和匿名內聯元素(虛線邊框)。
我們清楚的看到,第二個line-box明顯比其他的line-box要更高,根據子元素的內容區域(content-area)計算得來,更具體地說,是使用了Catamaran字體。
困難的是line-box創建部分是我們無法看到的,也不是用CSS控制它。即使在::first-line應用了background也無法直接在視覺上看到第個line-box的高度。
line-height問題
直到現在,我們介紹了兩個概念:content-area和line-box。如果仔細閱讀了前面的內容,你應該知道line-box的高度是根據子元素高度來計算,而且我并沒有說是子元素的內容區域(content-area)的高度。這是有很大區別的。
盡管這聽起來可能有些奇怪,內聯元素有兩個不同高度:內容區域(content-area)高度和虛擬區域(virtual-area)高度(這是我發明的術語virtual-area高度,你在規范中是找不到任何相關的內容)。
- 內容區域高度是由字體來決定的(前面介紹過)
- 虛擬區域(virtual-area)高度是line-height,它的高度用于計算line-box的高度
行內元素有兩個不同的高度。
也就是說,line-height普遍的看法是不同基線(baseline)的距離。在CSS中,它并不是這樣。
計算虛擬區域(virtual-area)和內容區域(content-area)高度差稱為leading。leading添加在內容區域頂部,另一半添加在內容區域底部。因此,內容區域總是在虛擬區域的中間。
根據其計算值,line-height(virtual-area)相同情況下比content-area更高或更低。對于較小的virtual-area,leading是負值和line-box要比它的子元素更小。
還有其他的內聯元素:
- 替代內聯行內元素(<img>,<input>,<svg>等)
- inline-block元素
- 行內元素參與特定格式化上下文(如,Flexbox元素,和所有的Flex項目)
對于這些特定的行內元素,高度計算基于他們的height、margin和border屬性。如果hegiht的值是auto,然后使用line-height時content-area嚴格上等于line-height。
無論如何,我們仍然面臨的問題是line-height的normal值是多小?答案是,其計算content-area高度還是依據于里面的字體來度量。
我們回到FontForge。Catamaran的em-square是1000,但我們看到ascender/descender的值:
- 生成的Ascent/Descent: ascender是770,descender是230。用于繪制字符(OS/2)
- 度量的Ascent/Descent: ascender是1100,descender是540。用于內容區域高度(hhea和OS/2)
- 度量線的間距:通過Ascent/Descent度量使用line-height: normal(hhea)
在我們的示例中,Catamaran字體定義了0個單位的線間距(Line Gap),因此line-height: normal的值將等于內容區域,也就是1640個單位或1.64。
作為比較,Arial字體的一個em-square是2048個單位,其ascender是1854,descender是434,線間距是67。這意味著,font-size: 100px的內容區域是112px(1117個單位)和line-height是115px(1150個單位或1.15)。所有這些度量都是特殊字型,由字體設計師來設置。
顯而易見,設置line-height:1是一個非常糟糕的做法。我提醒你,font-size沒有單位的觀念是相對的,但內容區域不是相對的以及處理虛擬區域小于內容區域有很多問題存在。
但并是只有line-height:1。不論真假,我電腦上安裝了1117種字體(是的,我安裝了所有的Google Web字體),其中1059種字體,占全部字體的95%左右,計算的line-height大于1。它們計算line-height是從0.618到3.378。你得記住,是3.378!
line-box計算的小細節:
- 對于內聯元素,padding和border增加了其background區域,但不會增加內容區域高度(甚至是line-box高度)。因此,你在屏幕上看到的不一定就是內容區域。margin-top和margin-bottom對內聯元素不生效。
- 對于行內替代元素,inline-block和blocksified行內元素,padding,margin和border都會增加高度,所以內容區域和line-box的高度也會增加
vertical-align:一個屬性控制一切
前面我沒有提到vertical-align屬性,即使它是計算line-box高度的一個重要因素。我們甚至可以說,vertical-align屬性對于行內格式化上下文中的leading有很大的作用。
vertical-align的默認值是baseline。你注意到度量字體的ascender和descender?這些值是基于baseline,具有一定的比例。那么ascender和descender之間的比例真的是50/50,它可能會產生意想不到的結果,例如所有兄弟元素。
先從這個代碼開始:
<p><span>Ba</span><span>Ba</span> </p>p {font-family: Catamaran;font-size: 100px;line-height: 200px; }兩個<span>元素繼承了<p>元素的font-family,font-size和固定的line-height。基線將會匹配以入line-box的高度等于他們的line-height。
如果第二個元素設置更小的font-size呢?
span:last-child {font-size: 50px; }這聽起來很奇怪,但默認基線對齊可能導致更高的line-box,如下圖所示。我提醒你,line-box的高度是從它的子元素最高點和最低點計算。
有一個觀點可以得到支持,那就是line-height設置不帶任何單位的值,但有時你需要做一個完美的Vertical-rhythm。說實話,不管你選擇什么,你總是會有困難的。
看看另一個例子。<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>元素有自己的字體(默認是serif)。<p>和<span>之間的基線可能是不同的,因此line-box的高度是高于預期的。這是因為瀏覽器給每個line-box計算都是開始于一個任意字符。規范中稱之為strut。
一個看不見的角色,但的確是會有可見的影響。
就我自己一些經歷,我們將面臨同樣的問題,那就是兄弟元素。
基線對齊是完了,但vertical-align:middle可以拯救它們?可以閱讀規范:
Middle “aligns the vertical midpoint of the box with the baseline of the parent box plus half the x-height of the parent”.
基線的比例不同,以及x-height比例,所以中間對齊是不可靠。最壞的情況下,在大多數的情況下,中間就從來沒有真的中間過。這里面有太多的因素參與其中,使用CSS是不能設置這些因素的(x-height,ascender和descender比例等)。
它有四個值,這可能在某些情況下是有用的:
- vertical-align: top | bottom和line-box的頂部或底部對齊
- vertical-align: text-top | text-bottom和內容區域的頂部或底部對齊
注意了,在所有情況下它都是在虛擬區域中,所以是看不見的高度。看看這個簡單的示例,使用vertical-align:top,看不見的line-height可能產生一些很奇怪的結果。
最后,vertical-align還能接受數值,提高或降低盒子的基線。最后一個選項可以派上用場。
CSS 無所不能
我們已經討論過了line-height和vertical-align在一起是如何工作,但現在的問題是如何使用CSS來控制字度的度量指標?簡短的回答:沒有。即使真的如此,我也想我們應該可以做些什么?那么有關于字體度量,我們應該能夠做些什么?
例如,如果我們想要給文本使用Catamaran字體,可以把其capital高度擴展到100px?通過一些數學計算,似乎可行。
首先設置度量字體的五個自定義屬性,然后計算font-size,從而得到capital高度是100。
p {/* font metrics */--font: Catamaran;--capitalHeight: 0.68;--descender: 0.54;--ascender: 1.1;--linegap: 0;/* desired font-size for capital height */--fontSize: 100;/* apply font-family */font-family: var(--font);/* compute font-size to get capital height equal desired font-size */--computedFontSize: (var(--fontSize) / var(--capitalHeight));font-size: calc(var(--computedFontSize) * 1px); }很簡單,不是嗎?但如果我們想要讓文本在可視區居中,讓剩余的空間均分在”B”字的頂部和底部,應該怎么做呢?為了達到這一目的,我們必須基于ascender和descender比例計算出vertical-align。
首先,計算line-height:normal和內容區域的高度。
p {…--lineheightNormal: (var(--ascender) + var(--descender) + var(--linegap));--contentArea: (var(--lineheightNormal) * var(--computedFontSize)); }這時,我們需要:
- 大寫字每底部距離底部邊緣的距離
- 大寫字母頂部距離頂部邊緣的距離
像這樣:
我們現在可以通過距離乘以font-size計算出vertical-align。
p {…--valign: ((var(--distanceBottom) - var(--distanceTop)) * var(--computedFontSize)); } span {vertical-align: calc(var(--valign) * -1px); }最后,我們設定所需的line-height和計算它,保持一個垂直對齊:
p {…/* desired line-height */--lineheight: 3;line-height: calc(((var(--lineheight) * var(--fontSize)) - var(--valign)) * 1px); }添加一個圖標和字母”B”垂直對齊,現在很容易就能做到:
span::before {content: '';display: inline-block;width: calc(1px * var(--fontSize));height: calc(1px * var(--fontSize));margin-right: 10px;background: url('https://cdn.pbrd.co/images/yBAKn5bbv.png');background-size: cover; }示例的地址可以點擊這里。
注意:這個測試只是出于演示目的。你不能依賴于此。如果字體不加載,備用字全有可能具有不同的字體度量參數,它就沒法正常工作了。
在一部分示例中,大家看到很有以–開頭的,這是CSS的原始變量,也稱之為CSS自定義屬性。
總結
這篇文章我們學到了什么:
- 行內格式化上下文真的很難理解
- 所有行內元素都有兩個高度
- 內容區域(content-area)基于字體的度量參數
- 虛擬區域(virtual-area)就是line-height
- 這兩個高度是無法可視的(如果你通過開發者工具,你可以看到)
- line-height:normal是基于字體度量參數
- line-height: n有可能創建一個虛擬區域比內容區域更小
- vertical-align不是很可靠
- 一個line-box的高度計算是基于它的子元素的line-height和vertical-align屬性
- 我們沒有辦法直接通過CSS來獲取或設置字體的度量參數
- 未來可能會有一個垂直對齊的規范來解決這些看似問題的問題:Line Grid Module
相關資源
- 獲取字體度量參數的工具:FontForge,opentype.js
- 在瀏覽器中計算line-height:normal和一些比例
- Ahem,一個特殊字體,幫助我們如何理解它怎么工作
- 一個更深,更透徹闡述行內格式化上下文
- 兩份詳細介紹line-height的PPT:CSS line-height和Deep dive line-height
博客名稱:王樂平博客
CSDN博客地址:http://blog.csdn.net/lecepin
總結
以上是生活随笔為你收集整理的深入了解CSS字体度量,行高和vertical-align的全部內容,希望文章能夠幫你解決所遇到的問題。