淺析行內元素視覺格式化

程式碼君的自白發表於2019-02-24

起因

前段時間組內同學在開發運營頁的過程中,遇到一個有趣的問題:移動端頁面採用 rem自適應佈局,圖片在垂直方向展示時會出現莫名的間隙(如下圖所示)。幾位同學紛紛提出不同的解決方案,但為什麼會出現這種問題呢?秉著知其然且知其所以然,藉此行文一篇,大致梳理行內元素視覺格式化方面理論。

描述

前言

上面的問題其實三言兩語就可以說個大概,不過擴散到相關知識點卻不能簡單的一語帶過。看過《CSS權威指南》的童鞋會知道,書中第七章有專門講視覺格式化方面理論。同時 CSS規範在第九、十章也分別有闡述使用者代理如何處理視覺格式化。相比塊級元素,行內元素的視覺顯示基本術語多也難理解。

塊級元素生成塊級框,通常不允許其他內容與這些框並存。在充分理解盒模型( BoxModel)後,水平和垂直方向格式化表現很容易預測。行內塊級元素可以說是行內元素和塊級元素的混合物,實際上行內塊級元素表現與行內替換元素相似。

行內元素的視覺顯示則涉及較多術語,例如行內替換元素、行內非替換元素、內容區、行內框、行框、字型大小、基線、 lin-heightvertical-align等,同時也會產生很多意思的問題。

注:後文討論主要為常規流( normal flow)中的行內元素( inlineelements)佈局,不包括浮動( float)、定位( position)元素。後文描述 box為“框”,也可為“盒”,相同意思不同翻譯。

快速複習

  • 常規流( normal flow):文件文字按照從左向右、從上向下順序顯示(自右而左語言相反)。脫離常規流可以採用浮動或定位方式。

  • 非替換元素( non-replaced elements):如果元素內容包含在文件中,則稱為非替換元素。例如包含文字的段落。

  • 替換元素( replaced elements):指用作為其他內容佔位符的一個元素。 img元素就是其中經典例子。

  • 塊級元素( block-level elements):塊級元素會在其框之前和之後生成"換行",處在正常流中的塊級元素會垂直襬放。

  • 行內元素( inline-level elements):行內元素不會獨佔一行,相鄰的行內元素會排列在同一行中。

  • 包含塊( containing block):CSS規範中制定一系列規則來確定元素的包含塊。對於正常流中元素,包含塊由最近塊級祖先框的內容邊界( content edge)構成。詳情見CSS規範10.1章節 Definitionof"containing block",注意與塊容器區分( block container)。

注:上述部分內容引自《CSS權威指南 第三版》第七章

在進一步介紹行內格式化之前,先來回顧一些行內佈局的基本術語。(略枯燥,不過明白概念才能更好理解視覺效果)

基本術語

  • 匿名文字( anonymous text):指所有未包含在行內元素的字串。 CSS規範中對匿名文字所處上下文分別有匿名塊級框( anonymous block boxes)和匿名行內框( anonymousinlineboxes)生成規則。(詳情見CSS規範:9.2.1.1、9.2.2.1章節)

  • 匿名行內框( anonymousinlineboxes):任何被直接包含在一個塊容器元素中(不在行內元素裡面)的文字,必須視為一個匿名行內元素,生成匿名行內框。

  • 內容區( content area):在非替換元素中,內容區可以是元素中各字元的em框串在一起構成的框,也可以是由元素中字元字形描述的框。替換元素中,內容區是元素固有寬高加上可有的外邊距、邊框或內邊距。注意非替換元素內容區的高度應該基於字型, CSS規範對此沒有作具體說明。

  • 行間距( leading):行間距為 line-heightfont-size值之差。差值分為兩半(半間距 half-leading)分別應用到內容區的頂部和底部。注意行間距只應用於非替換元素,可以為負值。

  • 行內框( inlineboxes):行內框是通過向內容區增加行間距來描述。對於非替換元素,元素行內框的高度剛好等於 line-height值。對於替換元素,元素行內框的高度等於內容區高度。

  • 行框( line boxes):包含同一行中出現的行內框的最高點和最低點的最小框。換句話說,行框的上邊界要位於最高行內框的上邊界,而行框的底邊要放在最低行內框的下邊界。

注:上述部分內容引自《CSS權威指南 (第3版)》第七章

以上術語在初次閱讀情況下,看起來還是比較費解,建議參照《CSS權威指南(第3版)》第七章理解。如下圖所示:假如行內非替換元素的 font-size為15px, line-height為21px,則行間距為6px。相同行只存在唯一行內框,所以行框高度和行內框相同都為21px。

行內術語解釋

對於行框和行內框關係,可以簡述為:行內框是由行內元素產生,行框則是一行中所有的行內框構成。

在繼續介紹之前,有必要了解行內如何逐步構造一個行框,並且通過格式化過程清楚如何確定各部分高度。

視覺格式化流程

  1. 按照以下步驟確定行中各元素行內框以及高度:

    1. 獲取各行內非替換元素及不屬於後代內元素的所有文字的 font-size值和 line-height值, line-heightfont-size差值為行間距值。

    2. 獲取替換元素高度及上、下外邊距,上、下內邊距,上、下邊框值相加。

  2. 對於內容區,需要確定各元素、匿名文字以及該行本身基線的位置,將其基線對齊。對於替換元素,需要底邊放置該行基線上。

  3. 對於指定 vertical-align 值的元素,確定其垂直偏移量。並改變元素在上方或下方超出的距離。

  4. 確定各行內框的具體位置後再進行確定行框。行框高度為最高行內框頂端和最低行內框低端之間的距離。

其他要點

  1. 行內元素的背景應用於內容區及所有內邊距。

  2. 行內元素的邊框要包圍內容區及所有內邊距和邊框。

  3. 垂直方向替換元素和非替換元素的視覺表現不盡相同。非替換元素的內、外邊距和邊框對其生成的框沒有垂直效果,它們不會影響元素行內框的高度,以及該元素所在行框的高度。

  4. 替換元素的外邊距和邊框會影響行內框高度,也可能影響行框的高度。

上述流程已經對於行內元素格式化做一個大概介紹。但其中還有些細節問題,如何確定行框的基線位置,行內替換元素為什麼會出現空隙(文章首部圖所示), line-height如何影響行高, vertical-alignline-height又有何關係, vertical-align又是如何影響視覺格式化等等。

如何確定行框的基線

在行內相關模型中,凡是涉及到垂直方向的排版或對齊,都離不開最基本的基線( baseline)。那基線又是如何確定的呢?

匿名文字x的下邊緣(線)

這裡指不包含在行內元素中的匿名行內框裡面的字元 x的底部。具體測試方法可在包含塊中新增匿名文字 x檢視與該行圖片底部位置,如下圖所示。

基線示例

演示請點選 codepen.io/sunpeijun/p…

行內替換元素間為什麼會出現空隙

回到開篇所提到的問題,如上圖所示,兩張圖片間為什麼會出現莫名間隙?前面鋪墊那麼多,也是為了這塊說道時更方便。首先這裡有個前提,圖片元素為行內替換元素,html採用 rem佈局,設定根元素 font-size為100px,圖片元素 font-size繼承根元素節點值100px。程式碼結構如下:

<div>  <img src='./images/bg1.jpg'>  <img src='./images/bg2.jpg'></div>複製程式碼

演示請點選 codepen.io/sunpeijun/p…

很明顯的是每張圖分別處於一個行框中。對於 line-height預設值 CSS規範中建議使用者代理設定介於1.0到1.2。同時 line-height值是相對於元素本身的 font-size值計算,則這裡圖片元素 line-height為100px(假設使用者代理設定 line-height預設為1)。圖片作為行內替換元素,其在行內預設會與行框基線對齊。這裡使用者代理會產生空的匿名文字行內框,框的高度則等於 line-height值。

由於高度值較小圖片元素生成的行內框高度小於匿名文字行內框值(100px),行框高度為同一行中出現的行內框的最高點和最低點的最小框,很容易看出這裡行高為100px。圖片高度小於行高,視覺上會出現圖片周圍存在空白,就是上圖中所示莫名空隙。其實主要原因還是 line-height值過大撐起行框導致。

那麼如何解決這個問題呢?

  1. 設定圖片包含塊( containing block) line-height:0,此時空匿名文字 line-height繼承包含塊值,行內框值為0。那麼行框邊界則為圖片元素生成的行內框最高點和最低點,行框高度等於圖片原有高度。

  2. 設定圖片包含塊( containing block) font-size:0,空匿名文字框繼承 font-size值, line-height計算值也為0,原理同上。

  3. 設定圖片元素 display:block。這麼做目的是阻止產生行內格式化模型,因此也不存在 lin-height計算值、行框、行間距屬性。

  4. 設定圖片包含塊樣式屬性為 display:flex;flex-direction:column;,本質上也是阻止產生行內格式化上下文。

演示請點選 codepen.io/sunpeijun/p…

附: CSS規範中對空匿名文字行內框描述(詳情見 CSS規範 10.8.1章節)

each line box starts with a zero-width inline box with the element's font and line height properties.

line-height 是否絕對等於行高

這裡也可以換種表述方式, line-height屬性和行框的高度有何關係。查閱 CSS規範, line-height描述如下:

On a block container element whose content is composed of inline-level elements, 'line-height' specifies the minimal height of line boxes within the element. The minimum height consists of a minimum height above the baseline and a minimum depth below it, exactly as if each line box starts with a zero-width inline box with the element's font and line height properties.

筆者不才,嘗試翻譯。對於一個內容由行內級元素組成的塊容器元素, line-height指定元素內行框的最小高度。這個最小高度包含基線上方的最小高度和下方的最小深度。就像每個行框以一個具有該元素字型和行高屬性的寬度為0的行內框開始。(筆者注:也就是上文提到空匿名文字行內框)

注意規範中有明確表示, line-height指定元素內行框的最小高度。由此看來 line-height值並不是絕對等於行框的高度。那如何去理解最小高度呢?其實這個問題的範疇已經在上文隱式提及過。

首先行框中包含替換元素和非替換元素,對於非替換元素,元素行內框的高度則等於 line-height值。對於替換元素, line-height屬性無效,元素行內框的高度等於內容區高度(見上文基礎術語行內框定義)。參照上文 視覺格式化流程章節,假如行框記憶體在替換元素( img)的行內框高度大於非替換元素行內框高度( line-height值),又因行框的高度為行內框的最高點到最低點距離,所以此時行框的高度要大於 line-height值。

那什麼情況下,行高等於 line-height值,也就是前面所說的最小高度呢?顯而易見,行框中只存在非替換元素或存在替換元素,但替換元素行內框要小於 line-height值。(多個行內框的情況下需具有相同 line-height值)

line-height 與 vertical-align 有何關係

談及 line-heightvertical-align關係,張鑫旭老師有篇博文描述為基友。為何基友呢,拋個問題: line-height值為百分比時如何計算, vertical-align值為百分比時又是相對於誰呢?其實前者相對於元素 font-size計算,後者相對於元素 line-height值計算。

心生疑惑, vertical-align百分比值為何要相對於 line-height值計算?一般我們認為元素在垂直方向相關屬性百分比值,多是根據元素包含塊的高度或本身高度計算,比如 height、top、translateY等。不過對於行內非替換元素垂直方向 height、margin、padding屬性是不生效的,那麼退而求其次能確定的也就是 line-height值了。基友關係也就油然而生,個人理解非權威。

vertical-align 如何影響視覺格式化

再來看一個栗子,和 行內替換元素間為什麼會出現空隙章節文件結構一致,兩張圖片包含在塊級元素中,但並未設定 html元素 font-size:100px;屬性。為減少圖示方便說道,新增匿名文字 x作為輔助,如下圖所示。

演示請點選 codepen.io/sunpeijun/p…

首先這裡並沒有設定html元素 font-size:100px,行內框的高度也不會出現過大(等於100px)現象,那為什麼還會出現空白間隙呢?

因為空匿名文字行內框。

行內替換元素垂直方向預設採用 baseline對齊,替換元素底部放置在行框基線上。此時行內會產生空匿名文字框確定行框基線位置(以上圖字元 x行內框為例)。但文字行內框的底部在基線下面,且行框的底端應包含文字框底端。也就是說行框包含了基線位置下面空白。圖示間隙其實是文字框基線以下至文字框底部的區域。

知其原因後,解決起來也就順理成章。除上文 行內替換元素間為什麼會出現空隙章節提及的方法可用外,這裡我們還可以使用 vertical-align屬性來解決。圖片元素設定:

img {  vertical-align: top;}複製程式碼

演示請點選 codepen.io/sunpeijun/p…

為什麼? vertical-align:top屬性使圖片元素垂直方向與行框頂部對齊,自然文字框底部也不再是行框的底部,圖片生成行內框的高度也就成為行框的高度(限於 line-height值小於圖片高度情形)。

參閱規範, vertical-align屬性會影響由一個行內級元素生成行內框在行框內部的豎直定位。由此可知, vertical-align在不同屬性值情況下,對行框的佈局和高度會產生相應影響。

vertical-align: middle 是否絕對垂直居中

vertical-align還有個耐人琢磨的屬性值: middleCSS規範定義其為:

Align the vertical midpoint of the box with the baseline of the parent box plus half the x-height of the parent.

簡譯之,把該框的垂直中點和父級框的基線加上父級的半 x-height對齊。 x-height又是什麼意思,其實這裡是指小寫字元 x的高度。術語描述就是基線和等分線( mean line)(也稱作中線( midline))之間的距離。維基百科中圖示如下:

x-height

vertical-align:middle其實並不會絕對垂直居中,我們平常看到的 middle只是一種近似的效果。原因很簡單,因為不同的字型,其在行內盒子中的位置是不一樣的。

比方說“微軟雅黑”就是一個字元下沉比較明顯的字型,所有字元的位置相比其他字型要偏下一點。你會發現圖示和文字不在一條線上,而是相對於字元 x的中心位置對齊,我們肉眼看上去就好像和文字居中對齊了。

順便延伸下, height=line-height處理方式對於單行文字來說是絕對的垂直居中嗎?

注:上述部分內容引自張鑫旭老師博文《字母’x’在CSS世界中的角色和故事》。

結語

儘管 CSS格式化模型的某些方面乍看起來有些不太直觀,不過多熟悉一些就會發現這其中是有道理的。相對於塊級元素,行內元素在視覺格式化方面更為晦澀難懂。同時行內元素還要考慮替換元素和非替換元素情形,替換元素的加入也使得視覺格式化方面略顯複雜。

此外行內元素涉及到的概念或術語多是不可見的,比如匿名框( anonymous boxes)、行內框( inlineboxes),行框( line boxes),但實際卻起著重要作用。同時又有 font-size、line-height、vertical-align屬性左右著行內佈局。一知半解或淺嘗輒止終不能深入,靜下來慢慢閱讀會收穫頗豐。

本篇文章就書寫至此,如果想對格式化方面理解更深,還請多讀《CSS權威指南》多讀 CSS規範。書中自有千鍾粟。

初寫技術文章,多擔憂某處誤導讀者。下筆數日,仍會有不嚴謹之處。如有疑問或錯誤,敬請斧正,先行謝過。

附錄

參考文獻

  1. 《CSS權威指南(第3版)》

  2. CSS 2.1規範

  3. CSS 2.2規範

  4. CSS 2.1中文譯文

  5. CSS深入理解vertical-align和line-height的基友關係

  6. 字母’x’在CSS世界中的角色和故事

  7. 維基百科:x-height

                                      淺析行內元素視覺格式化

                                       打個廣告,歡迎關注筆者的公眾號


相關文章