摘要:接下來說句聽起來很奇怪的話一個(gè)內(nèi)聯(lián)元素有兩個(gè)高度高度和實(shí)際區(qū)域高度是我自己發(fā)明的單詞,它表示對(duì)人類有效的高度,你在其他地方是看不到這個(gè)單詞的。你沒看錯(cuò),是計(jì)算的一些細(xì)節(jié)對(duì)于內(nèi)聯(lián)元素,和會(huì)增大區(qū)域,但是不會(huì)增大不是的高度。
本文為饑人谷講師方方原創(chuàng)文章,首發(fā)于 前端學(xué)習(xí)指南。
這是一篇譯文,對(duì) inline 和 inline-block 的元素剖析非常給力。
原文:Deep dive CSS: font metrics, line-height and vertical-align - Vincent De Oliveira
line-height 和 vertical-align 都是簡(jiǎn)單的 CSS 屬性,以致于大多數(shù)人自以為知道這兩個(gè)屬性的工作原理。但實(shí)際上這兩個(gè)屬性非常復(fù)雜,也許算得上是 CSS 里最難的兩個(gè)屬性,因?yàn)檫@兩個(gè)屬性和 CSS 里一個(gè)鮮為人知的特性息息相關(guān):內(nèi)聯(lián)格式化上下文(IFC)(譯者注:和 BFC 相對(duì)應(yīng))。
舉例來說,line-height 的值可以是一個(gè)長(zhǎng)度(length)或者是一個(gè)數(shù)字,它的默認(rèn)值是 normal。那么,normal 是什么呢?我們經(jīng)常將 normal 理解為 1,或者 1.2,甚至連 CSS 規(guī)格文檔都沒有提到這一問題。我們知道 line-height 的值為數(shù)字時(shí),表示的相對(duì)于 font-size 的倍數(shù),但問題在于,font-size:100px 對(duì)應(yīng)的文字在不同字體里的高度是不一樣的!那么 line-height 會(huì)隨著文字大小的改變而改變嗎? normal 真的表示 1 或者 1.2 嗎?vertical-align 又是如何被 line-height 影響的呢?
讓我們來深入理解一個(gè)不那么簡(jiǎn)單的 CSS 機(jī)制。
font-size下面是一段簡(jiǎn)單的 HTML 代碼,一個(gè) p 標(biāo)簽包含了 3 個(gè) span 標(biāo)簽,每個(gè) span 各自有一個(gè) font-family:
Ba Ba Ba
p { font-size: 100px } .a { font-family: Helvetica } .b { font-family: Gruppo } .c { font-family: Catamaran }
(譯者注:這幾款字體你的電腦上可能沒有)
font-size 相同,font-family 不同,得到的 span 元素的高度也不同:
為什么 font-size: 100px 不能得到相同高度的元素呢?我測(cè)量了一下每個(gè) span 的高度:Helvetica 115px,Gruppo 97px,Catamaran 164px。
乍看很奇怪,但是仔細(xì)想想,這么做又是很有道理的。原因在于字體本身,這是字體的原理:
一款字體會(huì)定義一個(gè) em-square,它是用來盛放字符的金屬容器。這個(gè) em-square 一般被設(shè)定為寬高均為 1000 相對(duì)單位,不過也可以是 1024、2048 相對(duì)單位。
字體度量都是基于這個(gè)相對(duì)單位設(shè)置的,包括 ascender、descender、capital height、x-height 等。注意這里面的值是允許相對(duì)于 em-square 出血(bleed outside)的(譯者注:大概可以理解為超出 em-square)
在瀏覽器中,上面的 1000 相對(duì)單位會(huì)按照你需要的 font-size 縮放。
我們把 Catamaran 字體放到 FontForge 中,分析它的字體度量:
em-square 是 1000
ascender 是 1100,descender 是 540。通過測(cè)試發(fā)現(xiàn),macOS 上的瀏覽器使用了 HHead Ascent 和 HHead Descent 值,Windows 上的瀏覽器使用了 Win Ascent 和 Win Descent(而且兩個(gè)平臺(tái)上的值不一樣)。我們還看到 Capital Height 是 680,X height 是 485。
這意味著 Catamaran 字體占據(jù)了 1100 + 540 個(gè)相對(duì)單位,盡管它的 em-square 只有 1000 個(gè)相對(duì)單位,所以當(dāng)我們?cè)O(shè)置 font-size:100px 時(shí),這個(gè)字體里的文字高度是 164px。這個(gè)計(jì)算出來的高度決定了 HTML 元素的 content-area(內(nèi)容區(qū)域),后面我會(huì)講到 content-area。你可以認(rèn)為 content-area 就是 background 作用的區(qū)域。
我們還能看出大寫字母的高度是 68px,小寫字母的高度(x-height)是 49px。所以 1ex = 49px,1em = 100px,而不是 164px。(真好,em 是基于 font-size,而不是基于計(jì)算出來的高度)
在繼續(xù)深入之前,說點(diǎn)相關(guān)的知識(shí)。當(dāng) p 元素出現(xiàn)在屏幕上時(shí),它可能包含了多行內(nèi)容,每行內(nèi)容由多個(gè)內(nèi)聯(lián)元素組成(內(nèi)聯(lián)標(biāo)簽或者是包含文本的匿名內(nèi)聯(lián)元素),每一行都叫做一個(gè) line-box。line-box 的高度是由它所有子元素的高度計(jì)算得出的。瀏覽器會(huì)計(jì)算這一行里每個(gè)子元素的高度,再得出 line-box的高度(具體來說就是從子元素的最高點(diǎn)到最低點(diǎn)的高度),所以默認(rèn)情況下,一個(gè) line-box 總是有足夠的高度來容納它的子元素。
每個(gè) HTML 元素實(shí)際上都是由多個(gè) line-box 的容器,如果你知道每個(gè) line-box 的高度,那么你就知道了整個(gè)元素的高度。
如果我們修改一下最初的 HTML 代碼:
Good design will be better. Ba Ba Ba We get to make a consequence.
那么就會(huì)得到 3 個(gè) line-box(寬度固定):
第一行和最后一行各有一個(gè)匿名內(nèi)聯(lián)元素(文本內(nèi)容)
中間一行包含兩個(gè)匿名內(nèi)聯(lián)元素和三個(gè) span
我們清楚地看到第二個(gè) line-box比其他兩個(gè)要高一些。因?yàn)榈诙欣锩娴淖釉匾驗(yàn)橛幸粋€(gè)用到了 Catamaran 字體的 span。
line-box 的難點(diǎn)在于我們看不見它,而且不能用 CSS 控制它。即使我們用 ::first-line 給第一行加上背景色,我們也看不出第一個(gè) line-box 的高度。
line-height目前我已經(jīng)提到了兩個(gè)概念:content-area 和 line-box。如果你仔細(xì)看了,會(huì)發(fā)現(xiàn)我說 line-box 的高度是根據(jù)子元素的高度計(jì)算出來的,而不是子元素的 content-area 的高度。這個(gè)區(qū)別大了。
接下來說句聽起來很奇怪的話:一個(gè)內(nèi)聯(lián)元素有兩個(gè)高度:content-area 高度和 virtual-area (實(shí)際區(qū)域?)高度(virtual-area 是我自己發(fā)明的單詞,它表示對(duì)人類有效的高度,你在其他地方是看不到這個(gè)單詞的)。
content-area 的高度是由字體度量定義的(見上文)
vitual-area 的高度就是 line-height,這個(gè)高度用于計(jì)算 line-box 的高度
這么一來,這就打破了一個(gè)長(zhǎng)久的謠言:line-height 表示兩個(gè) baseline 之間的距離。在 CSS 里,不是這樣的。
virtual-area 和 content-area 高度的差異叫做 leading。leading 的一半會(huì)被加到 content-area 頂部,另一半會(huì)被加到底部。因此 content-area 總是處于 virtual-area 的中間。
計(jì)算出來的 line-height(也就是 virtual-area 的高度)可以等于、大于或小于 content-area。如果 virtual-area 小于 content-area,那么 leading 就是負(fù)的,因此 line-box 看起來就比內(nèi)容還矮了。
還有一些其他種類的內(nèi)聯(lián)元素:
可替換的內(nèi)聯(lián)元素,如 img / input / svg 等
inline-block 元素,以及所有 display 值以 inline- 開頭的元素,如 inline-table / inline-flex
處于某種特殊格式化上下文的內(nèi)聯(lián)元素,例如 flexbox 元素中的子元素都處于 flex formatting context(彈性格式化上下文)中,這些子元素的 display 值都是「blockified」
這類內(nèi)聯(lián)元素,其高度是基于 height、margin 和 border 屬性(譯者注:好像漏了 padding)。如果你將其 height 設(shè)置為 auto 的話,那么其高度的取值就是 line-height,其 content-area 的取值也是 line-height。
我們目前依然沒有解釋 line-height:normal 是什么意思。要解答這個(gè)問題,我們又得回到 content-area 高度的計(jì)算了,問題的答案就在字體度量里面。
我們回到 FontForge,Catamaran 的 em-square 高度是 1000,同時(shí)我們還看到很多其他的 ascender/descender 值:
常規(guī)的 Ascent/Descent:ascender 是 770,descender 是 230,用于渲染字符。
規(guī)格 Ascent/Descent:ascender 是 1100,descender 是 540。用于計(jì)算 content-area 的高度
規(guī)格 Line Gap:用于計(jì)算 line-height: normal。
在 Catamaran 這款字體中,Line Gap 的值是 0,那么 line-height: normal 的結(jié)果就跟 content-area 的高度一樣,是 1640 相對(duì)單位。
為了對(duì)比,我們?cè)倏纯?Arial 字體,它的 em-square 是 2048,ascender 是 1854,descender 是 434,line gap 是 67。那么當(dāng) font-size: 100px 時(shí),
其 content-area 的高度就是 100/2048*(1854+434) = 111.72,約為 112px;
其 line-height: normal 的結(jié)果就是 100/2048*(67+1854+434) 約為 115px。
所有這些值都是由字體設(shè)計(jì)師設(shè)置的。
這么看來,line-height:1 就是一個(gè)很糟糕的實(shí)踐。記得嗎,當(dāng) line-height 的值是一個(gè)數(shù)字時(shí),其實(shí)就是相對(duì) font-size 的倍數(shù),而不是相對(duì)于 content-area。所以 line-height:1 很有可能使得 virtual-area 比 content-area 矮,從而引發(fā)很多其他的問題。
不僅僅是 line-height:1 有問題,我電腦上的 1117 款字體中,大概有 1059 款字體的 line-height 比 1 大,最低的是 0.618,最高的是 3.378。你沒看錯(cuò),是 3.378!
line-box 計(jì)算的一些細(xì)節(jié):
對(duì)于內(nèi)聯(lián)元素,padding 和 border 會(huì)增大 background 區(qū)域,但是不會(huì)增大 content-area(不是 line-box 的高度)。一般來說你無法再屏幕上看到 content-area。margin-top 和 margin-bottom 對(duì)兩者都沒有影響。
對(duì)于可替換內(nèi)聯(lián)元素(replaced inline elements)、inline-block 元素和 blockified 內(nèi)聯(lián)元素,padding、margin 和 border 會(huì)增大 height(譯者注:注意 margin),因此會(huì)影響 content-area 和 line-box 的高度
vertical-align我還沒提過 vertical-align 屬性,它也是計(jì)算 line-box 高度的重要因素之一。我們甚至可以說 vertical-align 是內(nèi)聯(lián)格式化上下文(IFC)中最重要的屬性。
它的默認(rèn)值是 baseline。還記得字體度量里的 ascender 和 descender 嗎?這兩個(gè)值決定了 baseline 的位置。很少有
字體的 ascender 和 descender 的比例是一比一的,所以我們經(jīng)??吹揭恍┮庀氩坏降默F(xiàn)象,下面是例子。
代碼如下:
Ba Ba
p { font-family: Catamaran; font-size: 100px; line-height: 200px; }
一個(gè) p 標(biāo)簽內(nèi)有兩個(gè) span 標(biāo)簽,span 繼承了 font-family、font-size 和 200px 的 line-height。這時(shí)兩個(gè) span 的 baseline 是等高的,line-box 的高度就是 span 的 line-height。
如果第二個(gè) span 的 font-size 變小了呢?
span:last-child { font-size: 50px; }
我們會(huì)發(fā)現(xiàn)一個(gè)非常奇怪的現(xiàn)象,line-box 的高度變高了!如下圖所示。提示你一下,line-box 的高度是從子元素的最高點(diǎn)到最低點(diǎn)的舉例。
這個(gè)例子可以作為「應(yīng)該將 line-height 的值寫成數(shù)字」的論據(jù),但是有時(shí)候我們?yōu)榱俗龀龊每吹呐虐妫仨毎?line-height 寫成一個(gè)固定值。
不過我實(shí)話告訴你吧,不管你把 line-height
寫成什么,你都會(huì)在對(duì)齊內(nèi)聯(lián)元素的時(shí)候遇到麻煩。
我們來看另一個(gè)例子。p 標(biāo)簽有 line-height:200px,內(nèi)含一個(gè) span,span 繼承了 p 的 line-height。
Ba
p { line-height: 200px; } span { font-family: Catamaran; font-size: 100px; }
此時(shí) line-box 的高度是多少?貌似是 200px,但其實(shí)不是。這里你沒有考慮到的問題是 p 有自己的 font-family,默認(rèn)值是 serif。p 的 baseline 和 span 的 baseline 位置不一樣,因此最終的 line-box 比我們預(yù)想的要高一些。出現(xiàn)這種問題是因?yàn)闉g覽器認(rèn)為每個(gè) line-box 的起始位置都有一個(gè)寬度為 0 的字符(CSS 文檔將其稱為 strut),并將其納入 line-box 的高度的計(jì)算中。
看不見的字符,看得見的影響。
為了說明這個(gè)問題,我們畫圖解釋一下這個(gè)問題。
用 baseline 來對(duì)齊令人費(fèi)解,如果我們用 vertical-align: middle 會(huì)不會(huì)好一點(diǎn)呢?讀 CSS 文檔你會(huì)發(fā)現(xiàn),middle 的意思是「用父元素 baseline 高度加上父元素中 x-height 的一半的高度來對(duì)齊當(dāng)前元素的垂直方向的中點(diǎn)」。baseline 所處的高度跟字體有關(guān),x-height 的高度也跟字體有關(guān),所以 middle 對(duì)齊也不靠譜。更糟糕的是,一般來說,middle 根本就不是居中對(duì)齊!內(nèi)聯(lián)元素的對(duì)齊受太多因素影響,因此不可能用 CSS 實(shí)現(xiàn)。
順便一說,vertical-align 的其他 4 個(gè)值有可能有點(diǎn)用:
vertical-align: top / bottom,表示與 line-box 的頂部或底部對(duì)齊
vertical-align: text-top / text-bottom,表示與 content-area 的頂部或底部對(duì)齊
不過你依然要小心,大部分情況下,對(duì)齊的是 virtual-area,也就是一個(gè)不可見的高度??纯聪旅孢@個(gè)用 vertical-align:top 的例子:
最后,vertical-align 的值也可以是數(shù)字,表示根據(jù) baseline 升高或降低,不到萬不得已還是別用數(shù)字吧。
CSS is awesome我們討論了 line-height 和 vertical-align 如果互相影響,現(xiàn)在問題來了:CSS 可以控制字體度量嗎?簡(jiǎn)單來說答案是:不行。我也很想用 CSS 來控制字體。無論怎樣,我還是想試試。字體度量只是一些固定的值而已,我們應(yīng)該可以圍繞它做點(diǎn)什么。
比如說,我們想要一段文字使用 Catamaran 字體,同時(shí)大寫字母的高度正好是 100px,看起來可以實(shí)現(xiàn),我們只需要一些數(shù)學(xué)知識(shí)。
首先我們把所有字體度量設(shè)置為 CSS 自定義屬性,然后計(jì)算出一個(gè) 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); }
看起來也并不復(fù)雜不是嗎?如果我們想要文字垂直居中怎么辦呢?也就是讓 B 上面的空間和下面的空間高度一樣。為了做到這一點(diǎn),我們必須要根據(jù) ascender 和 descender 的比例來計(jì)算 vertical-align。
首先計(jì)算出 line-height:normal 的值和 content-area 的高度:
p { … --lineheightNormal: (var(--fm-ascender) + var(--fm-descender) + var(--fm-linegap)); --contentArea: (var(--lineheightNormal) * var(--computedFontSize)); }
然后我們需要計(jì)算:
B 下面空間的高度
B 上面空間的高度
像這樣:
p { … --distanceBottom: (var(--fm-descender)); --distanceTop: (var(--fm-ascender) - var(--fm-capitalHeight)); }
然后我們就可以計(jì)算 vertical-align 的值。
p { … --valign: ((var(--distanceBottom) - var(--distanceTop)) * var(--computedFontSize)); } span { vertical-align: calc(var(--valign) * -1px); }
最后,設(shè)置 line-height:
p { … /* desired line-height */ --line-height: 3; line-height: calc(((var(--line-height) * var(--capital-height)) - var(--valign)) * 1px); }
添加一個(gè)和 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; }
JSBin 效果演示
注意這只是為了演示,請(qǐng)不要在生產(chǎn)環(huán)境中使用此方案。
結(jié)論我們知道了:
IFC 真的很難懂
所有的內(nèi)聯(lián)元素都有兩個(gè)高度
基于字體度量的 content-area
virtual-area(也就是 line-height )
這兩個(gè)高度你都無法看到
line-height: normal 是基于字體度量計(jì)算出來的
line-height: n (n=1,2,3…) 可能得出一個(gè)比 virtual-area 還要矮的content-area
vertical-align 不靠譜
line-box 的高度的受其子元素的 line-height 和 vertical-align 的影響
我們無法輕易的用 CSS 來控制字體度量
但是我依然喜歡 CSS :)
加饑人谷微信號(hào)進(jìn)入前端技術(shù)交流群: astak10 ,暗號(hào):寫代碼啦
每日一題,每周資源推薦,精彩博客推薦,工作、筆試、面試經(jīng)驗(yàn)交流解答,免費(fèi)直播課,群友輕分享... ,數(shù)不盡的福利免費(fèi)送
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/112900.html
摘要:看上面的例子我們也能看出來,實(shí)際上一個(gè)內(nèi)聯(lián)元素是有兩個(gè)高度的高度實(shí)際渲染的那個(gè)高度和高度實(shí)際區(qū)域占空間的高度也就是。 前言 總括: 本文通過實(shí)例講解CSS中最大的難點(diǎn)之一,行內(nèi)元素的布局,主要是挖掘line-height和vertical-align兩個(gè)屬性在布局方面的使用。 原文博客地址:深入理解行內(nèi)元素的布局 知乎專欄&&簡(jiǎn)書專題:前端進(jìn)擊者(知乎)&&前端進(jìn)擊者(簡(jiǎn)書) 博...
摘要:看上面的例子我們也能看出來,實(shí)際上一個(gè)內(nèi)聯(lián)元素是有兩個(gè)高度的高度實(shí)際渲染的那個(gè)高度和高度實(shí)際區(qū)域占空間的高度也就是。 前言 總括: 本文通過實(shí)例講解CSS中最大的難點(diǎn)之一,行內(nèi)元素的布局,主要是挖掘line-height和vertical-align兩個(gè)屬性在布局方面的使用。 原文博客地址:深入理解行內(nèi)元素的布局 知乎專欄&&簡(jiǎn)書專題:前端進(jìn)擊者(知乎)&&前端進(jìn)擊者(簡(jiǎn)書) 博...
摘要:給一個(gè)內(nèi)聯(lián)元素設(shè)置背景,這背景所占的區(qū)域就可以看成內(nèi)容區(qū)域??戳撕芏辔恼伦詈罂偨Y(jié)和整理了這些,下一篇介紹說說在內(nèi)聯(lián)元素中的作用。 在前端開發(fā)中,似乎控制不同大小文字之間,或者文字與圖標(biāo)之間對(duì)齊總是不那么得心應(yīng)手,總覺得少了點(diǎn)什么,這其中其實(shí)跟CSS中幾種跟內(nèi)聯(lián)元素相關(guān)的屬性有很大的關(guān)系,從我的感覺看,平時(shí)遇到的30%的css問題都是由于搞不清這幾種屬性的關(guān)系有關(guān)。這里第一篇文章先介紹字...
摘要:給一個(gè)內(nèi)聯(lián)元素設(shè)置背景,這背景所占的區(qū)域就可以看成內(nèi)容區(qū)域??戳撕芏辔恼伦詈罂偨Y(jié)和整理了這些,下一篇介紹說說在內(nèi)聯(lián)元素中的作用。 在前端開發(fā)中,似乎控制不同大小文字之間,或者文字與圖標(biāo)之間對(duì)齊總是不那么得心應(yīng)手,總覺得少了點(diǎn)什么,這其中其實(shí)跟CSS中幾種跟內(nèi)聯(lián)元素相關(guān)的屬性有很大的關(guān)系,從我的感覺看,平時(shí)遇到的30%的css問題都是由于搞不清這幾種屬性的關(guān)系有關(guān)。這里第一篇文章先介紹字...
閱讀 3607·2021-10-09 09:41
閱讀 2799·2021-10-08 10:18
閱讀 2252·2021-09-10 10:51
閱讀 2737·2021-09-10 10:50
閱讀 853·2021-09-09 09:33
閱讀 3467·2021-09-06 15:14
閱讀 3091·2019-08-30 11:06
閱讀 3313·2019-08-29 14:04