1. 程式人生 > >深入了解CSS字體度量,行高和vertical-align

深入了解CSS字體度量,行高和vertical-align

1.2 重要 經歷 ase 規範 並且 來看 以及 base

line-heightvertical-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-alignline-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字體的高度是115pxGruppo字體的高度是97pxCatamaran字體的高度是164px

技術分享圖片

起初似乎有點奇怪,但它是完全可預期的。這主要還是font-family的原因。那就要搞清楚它是如何工作的:

  • 字體定義其em-square,每個字符將會繪制出自己的容器。這個正方形使用相對單位和生成一個1000單位。但它也可以是10242048或者其他
  • 根據其薦對單位,字體的度量可以根據一些設置(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 = 49px1em = 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-arealine-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項目)

對於這些特定的行內元素,高度計算基於他們的heightmarginborder屬性。如果hegiht的值是auto,然後使用line-height時content-area嚴格上等於line-height

技術分享圖片

無論如何,我們仍然面臨的問題是line-heightnormal值是多小?答案是,其計算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的內容區域是112px1117個單位)和line-height115px1150個單位或1.15)。所有這些度量都是特殊字型,由字體設計師來設置。

顯而易見,設置line-height:1是一個非常糟糕的做法。我提醒你,font-size沒有單位的觀念是相對的,但內容區域不是相對的以及處理虛擬區域小於內容區域有很多問題存在。

技術分享圖片

但並是只有line-height:1。不論真假,我電腦上安裝了1117種字體(是的,我安裝了所有的Google Web字體),其中1059種字體,占全部字體的95%左右,計算的line-height大於1。它們計算line-height是從0.6183.378。你得記住,是3.378

line-box計算的小細節:

  • 對於內聯元素,paddingborder增加了其background區域,但不會增加內容區域高度(甚至是line-box高度)。因此,你在屏幕上看到的不一定就是內容區域。margin-topmargin-bottom對內聯元素不生效。
  • 對於行內替代元素,inline-block和blocksified行內元素,paddingmarginborder都會增加高度,所以內容區域和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-familyfont-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-heightvertical-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));
}

這時,我們需要:

  • 大寫字每底部距離底部邊緣的距離
  • 大寫字母頂部距離頂部邊緣的距離

像這樣:

p {
    …
    --distanceBottom: (var(--descender));
    --distanceTop: (var(--ascender) - var(--capitalHeight));
}

我們現在可以通過距離乘以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-heightvertical-align屬性
  • 我們沒有辦法直接通過CSS來獲取或設置字體的度量參數
  • 未來可能會有一個垂直對齊的規範來解決這些看似問題的問題:Line Grid Module

文章來源:http://www.w3cplus.com/css/css-font-metrics-line-height-and-vertical-align.html

深入了解CSS字體度量,行高和vertical-align