起因
前段時間組內同學在開發運營頁的過程中,遇到一個有趣的問題:移動端頁面採用 rem自適應佈局
,圖片在垂直方向展示時會出現莫名的間隙(如下圖所示)。幾位同學紛紛提出不同的解決方案,但為什麼會出現這種問題呢?秉著知其然且知其所以然,藉此行文一篇,大致梳理行內元素視覺格式化方面理論。
前言
上面的問題其實三言兩語就可以說個大概,不過擴散到相關知識點卻不能簡單的一語帶過。看過《CSS權威指南》的童鞋會知道,書中第七章有專門講視覺格式化方面理論。同時 CSS
規範在第九、十章也分別有闡述使用者代理如何處理視覺格式化。相比塊級元素,行內元素的視覺顯示基本術語多也難理解。
塊級元素生成塊級框,通常不允許其他內容與這些框並存。在充分理解盒模型( BoxModel
)後,水平和垂直方向格式化表現很容易預測。行內塊級元素可以說是行內元素和塊級元素的混合物,實際上行內塊級元素表現與行內替換元素相似。
行內元素的視覺顯示則涉及較多術語,例如行內替換元素、行內非替換元素、內容區、行內框、行框、字型大小、基線、 lin-height
、 vertical-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-height
和font-size
值之差。差值分為兩半(半間距half-leading
)分別應用到內容區的頂部和底部。注意行間距只應用於非替換元素,可以為負值。行內框(
inlineboxes
):行內框是通過向內容區增加行間距來描述。對於非替換元素,元素行內框的高度剛好等於line-height
值。對於替換元素,元素行內框的高度等於內容區高度。行框(
line boxes
):包含同一行中出現的行內框的最高點和最低點的最小框。換句話說,行框的上邊界要位於最高行內框的上邊界,而行框的底邊要放在最低行內框的下邊界。
注:上述部分內容引自《CSS權威指南 (第3版)》第七章
以上術語在初次閱讀情況下,看起來還是比較費解,建議參照《CSS權威指南(第3版)》第七章理解。如下圖所示:假如行內非替換元素的 font-size
為15px, line-height
為21px,則行間距為6px。相同行只存在唯一行內框,所以行框高度和行內框相同都為21px。
對於行框和行內框關係,可以簡述為:行內框是由行內元素產生,行框則是一行中所有的行內框構成。
在繼續介紹之前,有必要了解行內如何逐步構造一個行框,並且通過格式化過程清楚如何確定各部分高度。
視覺格式化流程
按照以下步驟確定行中各元素行內框以及高度:
獲取各行內非替換元素及不屬於後代內元素的所有文字的
font-size
值和line-height
值,line-height
和font-size
差值為行間距值。獲取替換元素高度及上、下外邊距,上、下內邊距,上、下邊框值相加。
對於內容區,需要確定各元素、匿名文字以及該行本身基線的位置,將其基線對齊。對於替換元素,需要底邊放置該行基線上。
對於指定
vertical-align
值的元素,確定其垂直偏移量。並改變元素在上方或下方超出的距離。確定各行內框的具體位置後再進行確定行框。行框高度為最高行內框頂端和最低行內框低端之間的距離。
其他要點
行內元素的背景應用於內容區及所有內邊距。
行內元素的邊框要包圍內容區及所有內邊距和邊框。
垂直方向替換元素和非替換元素的視覺表現不盡相同。非替換元素的內、外邊距和邊框對其生成的框沒有垂直效果,它們不會影響元素行內框的高度,以及該元素所在行框的高度。
替換元素的外邊距和邊框會影響行內框高度,也可能影響行框的高度。
上述流程已經對於行內元素格式化做一個大概介紹。但其中還有些細節問題,如何確定行框的基線位置,行內替換元素為什麼會出現空隙(文章首部圖所示), line-height
如何影響行高, vertical-align
與 line-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
值過大撐起行框導致。
那麼如何解決這個問題呢?
設定圖片包含塊(
containing block
)line-height:0
,此時空匿名文字line-height
繼承包含塊值,行內框值為0。那麼行框邊界則為圖片元素生成的行內框最高點和最低點,行框高度等於圖片原有高度。設定圖片包含塊(
containing block
)font-size:0
,空匿名文字框繼承font-size
值,line-height
計算值也為0,原理同上。設定圖片元素
display:block
。這麼做目的是阻止產生行內格式化模型,因此也不存在lin-height
計算值、行框、行間距屬性。設定圖片包含塊樣式屬性為
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-height
和 vertical-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
還有個耐人琢磨的屬性值: middle
, CSS
規範定義其為:
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
))之間的距離。維基百科中圖示如下:
vertical-align:middle
其實並不會絕對垂直居中,我們平常看到的 middle
只是一種近似的效果。原因很簡單,因為不同的字型,其在行內盒子中的位置是不一樣的。
比方說“微軟雅黑”就是一個字元下沉比較明顯的字型,所有字元的位置相比其他字型要偏下一點。你會發現圖示和文字不在一條線上,而是相對於字元 x
的中心位置對齊,我們肉眼看上去就好像和文字居中對齊了。
順便延伸下, height=line-height
處理方式對於單行文字來說是絕對的垂直居中嗎?
注:上述部分內容引自張鑫旭老師博文《字母’x’在CSS世界中的角色和故事》。
結語
儘管 CSS
格式化模型的某些方面乍看起來有些不太直觀,不過多熟悉一些就會發現這其中是有道理的。相對於塊級元素,行內元素在視覺格式化方面更為晦澀難懂。同時行內元素還要考慮替換元素和非替換元素情形,替換元素的加入也使得視覺格式化方面略顯複雜。
此外行內元素涉及到的概念或術語多是不可見的,比如匿名框( anonymous boxes
)、行內框( inlineboxes
),行框( line boxes
),但實際卻起著重要作用。同時又有 font-size、line-height、vertical-align
屬性左右著行內佈局。一知半解或淺嘗輒止終不能深入,靜下來慢慢閱讀會收穫頗豐。
本篇文章就書寫至此,如果想對格式化方面理解更深,還請多讀《CSS權威指南》多讀 CSS
規範。書中自有千鍾粟。
初寫技術文章,多擔憂某處誤導讀者。下筆數日,仍會有不嚴謹之處。如有疑問或錯誤,敬請斧正,先行謝過。
附錄
參考文獻
《CSS權威指南(第3版)》
打個廣告,歡迎關注筆者的公眾號