CSS > 行內格式化上下文中的各類高度計算

前言碎碎語:標題問題在昨天困擾了筆者好久好久,早上把問題提到了各網絡也暫時沒有回覆。由於明天要早起飛異地參加一場校招面試,筆者仍是很緊張的,但奈何問題不解決寢食難安……因此仍是卯起勁從新思考這個問題,算是暫時有了一個本身比較承認以及清晰的答案,與各位讀者分享。如您有不一樣觀點想法意見建議,懇請斧正!html

正式探討以前,咱們觀察一個現象(在 Chrome 下的表現,其餘瀏覽器下的表現和計算可能有細微差異):面試

上圖對應的 HTML 是(以後的探討均基於此):瀏覽器

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Line Height</title>
<style>
body {
    margin: 0;
    font: 32px/1 'Microsoft YaHei';
}
div {
    width: 400px;
    margin: 30px auto;
    outline: 1px solid black;
    background: #008E59;
}
img {
    height: 80px;
    margin-top: 10px;
}
</style>
</head>
<body>
    <div>
        <span>Some Text</span>
        <img src="picture.jpg" alt=""/>
    </div>
</body>
</html>

咱們來計算下 DIV 和 SPAN 的高度網絡

document.getElementsByTagName('div')[0].offsetHeight
//93
document.getElementsByTagName('span')[0].offsetHeight
//42

對於此圖,筆者產生以下疑問:測試

  • line-height 爲 32px,爲什麼 SPAN 的高度爲 42px?字體

  • DIV 的高度 93px,比 IMG 高度加外邊距 90px 以及 SPAN 高度 42px 都要大,如何計算的?網站

  • 圖片和文本下的空白(即使沒有文本同樣存在)是如何產生的?spa

假設咱們把 IMG 刪除,HTML 部分改成:設計

<body>
    <div>
        <span>Some Text</span>
    </div>
</body>

此時來計算:代理

document.getElementsByTagName('div')[0].offsetHeight
//32
document.getElementsByTagName('span')[0].offsetHeight
//42

新問題又來了:

  • DIV 的子元素高度爲 42px,爲什麼沒有「撐起」 DIV 的高度?

以上問題就是本文要探討的了。覆蓋了五個知識點:

  1. 行內盒(或行內不可替換元素)的高度

  2. 行內可替換元素的高度

  3. 行盒的高度

  4. 行距與行高

  5. 創建行內格式化上下文的塊盒的 auto 高度

因此在探討以前,筆者已假設您知道這些概念:行內盒、行內不可替換元素、行內可替換元素、行盒、行內格式化上下文。若是您還有點不清楚,咱們能夠快速補習下:

可替換元素、不可替換元素

簡單地講,可替換元素是指須根據其標籤和屬性來決定具體顯示內容的元素,如本文中會探討的 IMG 元素,其具體顯示內容由 src 等屬性決定; 不可替換元素則是內容直接呈現的元素。如本文會探討的 DIV 和 SPAN 等。

塊盒

此概念是塊格式化上下文的內容,要解釋起來就更復雜啦,筆者粗陋地給您一個描述:塊盒一般是 display: block 的不可替換元素。

行內級元素、行內級盒、行內盒、行內格式化上下文

display: inline|inline-table|inline-block 產生行內級元素。行內級元素生成行內級盒,而這些盒會參與行內格式化上下文。

display 值是 inline 的不可替換元素會生成一個行內盒。

不是行內盒的行內級盒被稱爲原子行內級盒。

行盒

在行內格式化上下文中,盒從包含塊的頂部一個接一個地水平擺放。包含了一行裏全部盒的矩形區域被稱爲行盒。一個段落就是多個行盒的垂直堆疊。

所以,咱們能夠獲得下圖(大體描摹):

如今開始計算!

1 行內可替換元素和文檔流內行內塊可替換元素高度計算

W3C 有明確規範,以下:

若是 heightwidth 計算值均爲 auto 且該元素有固有高度,那麼該固有高度爲 height 使用值。

不然,若是 height 計算值爲 auto,且該元素有一個固有比例,則 height 的使用值爲:

width 使用值 / 固有比例

不然,若是 height 計算值爲 auto,且該元素有固有高度,那麼該固有高度爲 height 使用值。

不然,若是 height 計算值爲 auto,但以上狀況均不符合,那麼 height 的使用值必須設定爲一個最大矩形的高度,該矩形比例爲2:1,高度不超過150px,且寬度不大於設備寬度。

所以,在咱們的實例中,IMG 盒的高度爲 80+10 = 90px。

2 行內盒的高度計算

「高度」一詞在這裏很有歧義,筆者認爲,總共能夠有三種概念須要辨析:

  • 行內盒的內容區域高度

  • 行內盒的盒高度

  • 計算行盒高度時的行內盒的盒高度

您可能對第二和第三解釋抱有疑問,但咱們先擱置懷疑,把清楚明白的東西先解決。

當咱們用 JavaScript 去獲取一個行內盒的 offsetHeight 值時,如咱們上面所作的:

document.getElementsByTagName('span')[0].offsetHeight

筆者將此高度稱做「行內盒的盒高度」,類比於咱們所熟知的塊盒盒高度。其計算值是:

內容區域高度 + 上下邊框 + 上下內邊距 = 行內盒的盒高度

邊框和內邊距的寬度默認爲 0,不然爲咱們本身指定,但「內容區域高度」是怎麼計算的呢?

W3C 這麼說:

height 不適用。內容區域的高度應基於字體,但本規範沒有指定如何。用戶代理可能,好比說,使用行高盒 EM-Box 或字體的最大上端部 Ascender 和下端部 Descender。(後一種會確保有部分在行高盒之上或之下的字符仍然落在內容區域內,但會致使不一樣字體有大小不一的盒子;前者則確保做者能夠控制相對於 line-height 的背景設計,但也致使字符繪製在其內容區域以外。)

言下之意:

  1. height 屬性無效

  2. 行內盒內容區域高度在規範裏面沒有定義,瀏覽器能夠本身折騰

既然規範沒有明確規定計算,咱們讓瀏覽器實測一下。筆者瀏覽器測試以下:

  • Chrome 42

  • IE11 42

  • Firefox 43

若是咱們更改字體,假設應用以下 CSS

body { font-family: Simsun; }
  • Chrome 33

  • IE11 37

  • Firefox 35

而若是咱們修改 line-height,以上結果均不受影響。

筆者也曾疑惑,這個 offsetHeight 就是內容區域高度嗎?答案:是。筆者的驗證方法是基於 W3C 以下規定:

儘管不可替換元素的外邊距、邊框以及內邊距不歸入行盒的計算,它們仍然渲染在行內盒的周圍。這意味着若是 line-height 指定的高度小於被包含盒的內容高度,內邊距和邊框的背景和顏色可能「流進」毗鄰的行盒。用戶代理應當按文檔順序渲染這些盒。這會形成後面的盒的邊框繪製在前面盒的邊框和文本上。

您能夠用如下代碼實測,會發現紅色行內盒的背景溢出到了黑色行內盒所在的行盒。

<div>
    <span style="background:red">Some Text</span>
    <br/>
    <span style="background:rgba(0,0,0,.5)">Some Text</span>
</div>

可知內容區域高度,即行內盒沒有內邊距和邊框時的 offsetHeight

所以總結論是:

行內盒的內容區域高度計算沒有統一的標準,不一樣的字體或者不一樣的瀏覽器均可能致使不一樣的結果,且其高度與 line-height 無關。

由此咱們沒法確切地得到一個跨瀏覽器的行內盒的內容區域高度。一樣咱們也沒法確切得到一個跨瀏覽器的行內盒高度(由於其計算式裏面就包括了不定變量內容區域高度)。

但問題來了,不一樣瀏覽器都採用不一樣的行內盒內容區域高度,又如何能統一計算行盒以及塊容器的高度呢?這個問題便致使了筆者在上面所提到的「計算行盒高度時的行內盒的盒高度」概念。

咱們進入下一個話題,行盒高度計算。

3 行盒高度計算

根據規範,行盒的高度決定以下:

  1. 計算行盒內每一個行內級盒的高度。對於可替換元素、行內塊元素以及行內表格元素,高度是其外邊距盒的高度;對於行內盒,高度是其 line-height

  2. 行內級盒根據其 vertical-align 屬性垂直對齊。若是它們對齊 topbottom,它們必須以能最小化行盒高度的方式對齊。若是這些盒足夠高,則有多種解決方案而且 CSS2.1 沒有規定此行盒的基線的位置。

  3. 行盒高度是最上盒頂部到最下盒底部的距離。

懂了:W3C 儘管容許瀏覽器有本身的行內盒內容區域計算方式,但統一了一個行盒高度的計算方式:

計算行盒的高度時,針對行內盒,高度直接取 line-height。行內盒能夠有邊框、內邊距、外邊距,然而跟行盒的高度徹底不要緊!

根據此規定,咱們很快能夠得出,計算行盒高度時,SPAN 盒的高度取 32px。

接着,因爲咱們的 vertical-align 是默認的 baseline,所以,應當把盒的基線同父盒的基線對齊。若是盒沒有基線,對齊盒的下外邊距邊緣與父盒的基線。也就是說,把 SPAN 盒的基線同 DIV 盒的基線對齊,把 IMG 盒的下外邊距邊緣同 DIV 盒的基線對齊。

下圖是字體的基線、上下端線等位置信息

圖片來源:http://blog.justfont.com/

筆者做圖以下:

假設咱們設 DIV 盒的基線是 0,則 IMG 盒的下邊緣同 DIV 盒基線對齊;上邊緣(上外邊距邊緣頂部)在高於基線 90px 處。而 SPAN 盒因爲其基線對齊 DIV 盒基線,故其行盒下邊緣略低於基線。

整個行盒的高度即 IMG 盒上邊緣到 SPAN 盒下邊緣。假設沒有 IMG 元素,則高度爲 SPAN 盒的 line-height

但讀者您可能注意到了,29 和 -3 是怎麼得來的呢?下面,筆者帶您算!

4 行距和行高計算

29 和 -3 兩值是在計算行距和行高後得來的。咱們先來看規範:

CSS 假設每種字體都由字體特性來指定一個基線之上的特性高度和之下的特性深度。本節中咱們用A表示(給定字體給定字號的)高度,用D表示深度。同時定義 AD = A + D,即從頂部到底部的距離。(參見下面如何找到TrueType和OpenType字體的A和D)注意該字體的這些特性是就整個而言的,無須對應任何個別字符的上端部和下端部。

用戶代理必須在一個不可替換行內盒中依照字符的相應基線對齊各個字符。接着,就每一個字符來決定A和D。注意單個元素的字符可能來自於不一樣字體所以不見得全部的A和D同樣。若是行內盒徹底不包含字符,則被視爲包含了一個具備元素首個可用字體的A和D的支柱(一個零寬度的不可見字符)。

接着對每一個字符添加行距L,其中 L = line-height - AD。行距的通常添加到A之上,另外一半添加到D之下,從而賦予字符以及其行距一個基線之上的完整高度 A' = A + L/2,以及完整深度 D' = D+ L/2。

注意。L可能爲負。

包含了全部字符以及字符兩側半行距的行內盒的高度正是 line-height

咱們在上述規定中接觸到了這些概念:特性高度 A,特性深度 D,頂部到底部距離 AD,完整高度 A',完整深度 D',行距 L。

關於特性值,筆者 Google 到一個網站,推薦讀者使用:

http://fontsgeek.com/

不得不吐槽下,國內真的很難找到這樣專業精緻的字體網站(也多是個人打開方式不對 >_<)。

好,咱們能夠得到咱們實例中 Microsoft YaHei 的字體特性了:Dcsender -536;Height 2703。

  • AD 即內容區域高度,在本例中是 42

  • D 即字體下端(基線之下)高度,爲 42*(536/2703) = 8

  • L = 32 - 42 = -10

  • 故,D' = 8 + -10/2 = 3

即知行內盒的下邊緣在基線之下 3px。同時行內盒的高度被視爲 32px,故亦知其上邊緣在基線之上 29px 處。

咱們說啦,整個行盒的高度即 IMG 盒上邊緣到 SPAN 盒下邊緣。因此得行內盒高度爲 90 + 3 = 93px。

5 創建行內格式化上下文的塊盒的 auto 高度

根據 W3C CSS2.1:10.6.3,該高度是從其上內容邊緣到其最後一個行盒的下邊緣。只考慮文檔流內子盒,絕對定位和浮動子盒應被忽略,相對定位子盒不考慮位移,子盒能夠是匿名盒。

在本例中,DIV 盒的行內格式化上下文僅有一個行盒,故其高度取該行盒高度,93px。

相關文章
相關標籤/搜索