處理垂直居中與隱藏屬性Font Metrics

最近在優化公司內部UI組件時,遇到了一個問題:
咱們的字體圖標在跟文字放在一塊兒時,若是不寫專門的樣式,看起來「沒有對齊」,如圖: css

示例1
那要怎麼作才能使圖標跟文字垂直居中呢?
可能,最早想到的是在圖標上添加 vertical-align: middle
示例2
呃,好像仍是沒有居中。根據經驗,在文字上也加上 vertical-align: middle
示例3
誒,這下好像基本對齊了。
那麼問題來了,是否是每次使用圖標加文本都須要這樣對齊呢?
若是個人圖標是出如今文本中間的,那又怎麼辦呢?
這個圖標跟文字真的絕對對齊了麼?這樣作是否是真的合理呢?
要解答這些問題,那得先從IFC提及……

IFC

IFC是什麼?
在理解浮動以及清理浮動的原理時,相信不少人都接觸過BFC(Block Formatting Contexts),定義了在「普通流」(Normal Flow)中,塊級盒子(Block Box)組成上下文中的表現特性。
而IFC,即Inline Formatting Context,顧名思義,定義了「行內盒子」(Inline Box)組成上下文中的表現特性,完整定義可見CSS標準文檔
根據IFC的描述,行內盒子(Inline Box)由「行內級元素」(Inline Level Elements)和「文本」(Contents)所組成,而行內盒子(Inline Box)水平排列所構成的一行矩形區域,叫作「行盒子」(Line Box)。
對應實際場景的話,字體圖標是一個display: inline-box的行內級元素,而「中文」這兩個字是另一個行內級元素,把這兩個元素造成的兩個行內盒子放在一塊兒,構成了一個行盒子。
除了display: inline-box的元素和文本是行內級元素外,display屬性爲「inline」,「inline-table」的元素以及像「img」、「input」、「video」等「替換元素」(Replaced Elements)都是行內級元素,但他們在行盒子中的高度計算方式不太相同。
替換元素的高度,等於它們自己的高度(含margin),而非替換元素的高度,則稍微有點複雜。
這裏,就要引出咱們故事的主角:字體度量(Font Metrics)。html

Font Metrics

簡單來講,字體度量(Font Metrics)就是字體中的一系列參數,這些參數對於CSS來講是不可見的,因此咱們須要藉助一些工具來查看,好比FontForge
拿中文字體經常使用的「微軟雅黑」爲例,能夠看到這些參數: git

示例7
這裏展現了一些基本信息,主要包括:
Em Size:單位字體所含的點的數量。也就是說,假設咱們給字體設置的大小爲100px,那在微軟雅黑中,每一個點分得 100 / 2048 的像素值。
Ascent:字符baseline到字符頂部的距離;
Descent:字符baseline到字符底部的距離;
經過Em Size,咱們也就能夠求得Ascent與Descent在實際場景中的具體像素值。可是,這裏的Ascent與Descent只是字體的通用屬性,實際渲染出來,每每會在字體的上下增長一些空間,並且在不一樣操做系統環境中,還不盡相同,以下圖:
示例6
這裏的Win Ascent與Win Descent是Windows系統下的值,而HHead Ascent與HHead Descent是Mac OS系統下的值。
此外,Capital Height表明該字體大寫字母的高度,X Height能夠理解爲字體的小寫字母x的高度。
下面借用一張圖,來更清晰的展現各個值之間的關係:
示例8
若是細心的話會發現,這裏的Ascent + Descent值竟然超過了Em Size?
沒錯,這也就說明,在實際的場景中,當給一個字體設置100px大小時,它的高度並不必定等於100px,好比這裏的話,就等於100 / 2048 * (2167 + 536) ≈ 132px。

Line Height

上面一節講到了,字體實際渲染的高度與所設定的字體大小不一致。那麼,對應到瀏覽器中,這個高度是什麼高度呢?
想必,你已經猜到了,這個就是默認的行高,也就是當line-height: normal時的字體行高: github

示例11
能夠看到,這裏的行高值跟咱們以前計算出來的高度是一致的。或者,也能夠這樣理解:對於微軟雅黑字體,設置 line-height: normal大體等於 line-height: 1.32
當行高的值大於 line-height: normal的值時,行間距(Leading)爲正值;當行高的值小於 line-height: normal的值時,行間距(Leading)爲負值。
通常狀況下,應該避免 line-height設置太小,不然行與行之間的文本可能會出現重疊。若是咱們給部分的字體設置了背景色,因爲背景色是覆蓋文本的實際高度的,這樣會形成視覺效果更糟糕:
示例12

Vertical Align

對於vertical-align這個css屬性,我曾經對它的實際做用苦惱了好久,好比:middle相對什麼對齊?toptext-top又有什麼區別?baseline究竟在哪裏等等。
那咱們就結合官方定義,來一個個看一下:面試

baseline

官方描述是,將行內盒子的baseline與其父級盒子的baseline對齊;若是行內盒子沒有baseline,就將它底部的margin邊界與父級盒子baseline對齊。
從前面的章節能夠看出,對於文原本說,它的baseline的高度,其實就是字體度量中Descent的高度。而對於替換元素,就如後半句的定義,以底部margin的邊界來對齊。
那麼問題來了,父級盒子的baseline在哪裏呢?
官方解釋說,每一個行盒子裏,最開始會有一個不可見的、零寬度的行內盒子,官方稱它叫「strut」。這個strut,能夠當作一個普通的文本,字體爲父級盒子所設置的font-family屬性。因此,vertical-align其實就是與這個隱藏的文本strut的對應參考線對齊。api

middle

vertical-align: middle是你們在處理垂直居中時,最經常使用到的屬性。然而,它有時候會出現不少怪異的場景,好比父級元素高度增長了。
仍是先來看下官方定義:將行內盒子垂直方向的中點與父級盒子的baseline以上的小寫字母x的一半的高度對齊。
簡單來講,就是參照小寫字母x的一半的那條線,將垂直高度的一半與之對齊。
由此能夠想象,設置了vertical-align: middle的行內盒子的相對位置會降低,在高度不變的基礎上,父級盒子便會高出它降低的這段高度。
根據定義,能夠很簡單的算出來:(XHeight / 2 + Descent) - [(Ascent + Descent) / 2 - Descent],即 [(1106 / 2 + 536) - (2703 / 2 - 536)] * (100 / 2048) ≈ 13px。 瀏覽器

示例14
示例13
此外,因爲不一樣字體的Ascent / Descent的值不一樣,設置了 vertical-align: middle後,未必從視覺上看,是必定對齊的。這也是文章最開始中,圖標跟字體看起來,尚未徹底對齊的緣由之一。

其餘

關於toptext-topbottomtext-bottom等的區別,這裏就不一一展開了,具體能夠參考官方定義文檔,或者參考下圖: 框架

示例15

字體與文本對齊

好了,繞了那麼大一圈,如今咱們回過來分析一下,最開始字體圖標對齊的問題。
字體圖標跟普通字體同樣,一樣能夠用FontForge來查看: ide

示例16
能夠看到,Descent的值爲0。也就是說,字體圖標的baseline的高度爲0,從而使得與文本放在一塊兒時,視覺上字體圖標是偏上的,也就是第一張圖所示的狀況。
當對字體添加 vertical-align: middle以後,根據定義,字體圖標會和小寫字母x對齊:
示例17
但若是是中文的話,默認狀況下,中文文本並無與小寫字母x垂直居中對齊,天然與字體圖標看上去也不是居中對齊的。
示例2
因而,當對中文文本設置了 vertical-align: middle以後,因爲兩遍同時以本身的垂直中線與同一條線對齊,視覺上相對就對齊了。
示例3

果然如此嗎?

若是眼見夠尖會發現,其實此時的字體圖標仍是略微偏上了。
仔細檢查一番,發現這個字體圖標並無撐滿其內容區域: wordpress

示例18
這是由於,在製做該圖形SVG的時候,線條並無撐滿畫布(鞭打一番設計師)。修改以後,終於徹底對齊了:
示例19

可是!!

這樣彷佛還有問題:
其一,前面也提到過,設置vertical-align: middle,可能帶來父級元素高度增長的反作用;
其二,每次使用時,都必須對字體圖標和文本添加vertical-align: middle,十分繁瑣;
其三,對於多段文本中穿插字體圖標的狀況,這一解決方案就不適用了。 在理想狀態下,默認對齊時,字體圖標與文本視覺上就應該是對齊的。
根據前文的分析,若是想要視覺上儘量對齊,對字體圖標設置一個合適的Descent是關鍵。
若是針對英文環境,這個相對容易一些:由於大寫英文字母,所有在baseline之上,且高度相對一致。部分小寫字母,會下探到baseline如下,如g、y,但總的字體高度也大體相同。參考字體生成平臺icomoon,其默認的baseline高度爲6.28%em,也就是對於1024個單位的字體,baseline設置爲64個單位。

示例20
中文環境相對麻煩一些,一方面不一樣漢字相對baseline下探的高度略有差別,另外一方面,還要確保仍然和英文字母對齊。這裏,主要參考了一些主流框架,設置了12.5%em的baseline高度,視覺上基本能使人滿意:
示例21

總結

IFC相關知識一直以來都是css中的一大難點,在面試中也常常會涉及。
可能有些狀況,嘗試了幾下就解決了,好比本人一開始的作法,對字體圖標和文本同時添加vertical-align: middle,但若是沒有完全弄清其中的原理的話,每每採用的並非最佳方式,甚至還會遇到一些「奇怪」的問題,好比高度增長。
但願經過本文,能幫助你們理解IFC,同時下面也列出了一些很好的參考文章供你們參考。本人在寫做中,也藉助這些文章,進一步加深並鞏固了相關知識。

參考資料

Deep dive CSS: font metrics, line-height and vertical-align
css行高line-height的一些深刻理解及應用

相關文章
相關標籤/搜索