CSS 中 html 與 body 的向上傳遞行為

以前寫的好深奧。

為什麼要討論這個東西?

讓我先問問看,你有沒有曾經碰過以下的問題,但卻不知道為什麼會這樣?

  • 為什麼 <body> 的高度不管是多少,它的 background-color 都會套用到整個畫面?
  • 為什麼 overflow 的預設值明明是 visible,但當內容超出 <body> 時,仍會產生滾軸?

這是因為有個叫 「Propagating behavior(傳遞行為)」 的機制,這個行為只會發生在 <body><html>這兩個元素上。

網頁最底層的背景顏色 Canvas

一個網頁中除了我們看到的畫面之外,它的最底層其實是一個「Canvas(畫布)」。

Canvas 是一個佔據整個網頁畫面的一張紙,我們網頁中所有的內容、樣式設定,都是放在這張畫布上面來呈現的。

關於 Canvas,W3C 的規範中提到了這一段:

Since no element corresponds to the canvas, in order to allow styling of the canvas CSS propagates the background of the root element (or, in the case of HTML, the <body> element).

大意是指:

既然網頁上沒有一個能夠代表 Canvas 的元素來讓我們直接設定,那就讓 CSS 藉由傳遞根元素(html)或是 <body> 的背景色,來對 Canvas 做設定。

也就是說,我們實際上都是透過根元素來設定 Canvas 的背景色,像這樣:

1
2
3
4
5
6
7
html {
// 背景色會被傳遞到Canvas
background-color: #ffa;
// 就算設定了高度,背景色仍顯示全屏,
// 因為 Canvas 的範圍是整個頁面
height: 100px;
}

如果覺得有點抽象的話,你也可以想像成是這樣子:

1
2
3
4
5
6
Canvas {
background-color: #ffa;
}
html {
height: 100px;
}

所以網頁最底層的背景色是由 Canvas 來決定的,<html> 只是負責把背景顏色傳遞給 Canvas 而已。

Codepen:

See the Pen html-background-color by jim (@jubeatt) on CodePen.


不過規範中似乎有提到 <body> 也能夠傳遞背景色給 Canvas,這又是怎麼一回事呢?

我們再來看個例子:

現在我們不對 <html> 做背景色的設定,而是對 <body> 設定背景色,看看會發生什麼:

1
2
3
body {
background-color: #ffa;
}

See the Pen body-background-color / When html doesn't have a backgroundColor. by jim (@jubeatt) on CodePen.


Wow,就和 <html> 一樣,<body> 把背景色給傳遞給 Canvas 了!

其實這個說法只對了一半,實際上是這樣子:

  1. <body> 先把背景色傳給 <html>
  2. <html> 再把背景色傳給 Canvas

這個部分我們再來看一段 W3C 的規範說明:

For documents whose root element is an HTML HTML element [HTML401] or an XHTML html element [XHTML11]: if the computed value of ‘background-image’ on the root element is ‘none’ and its ‘background-color’ is ‘transparent’, user agents must instead propagate the computed values of the background properties from that element’s first HTML BODY or XHTML body child element. The used values of that BODY element’s background properties are their initial values, and the propagated values are treated as if they were specified on the root element. It is recommended that authors of HTML documents specify the canvas background for the BODY element rather than the HTML element.

大意是指:

在規範中根元素為 <html> 的文件檔案(HTML 或 XHTML),如果根元素 “background-image” 的值為 “none” 且 “background-color” 的值為 “transparent”,則 user agents 必須以根元素中的 <body> 來傳遞 background 設定給 Canvas。看起來就跟指定值給根元素很類似,但這只是一個錯覺,實際上這個值是從 <body> 傳遞過去的。

另外,規範中建議以 <body> 來設置 Canvas 的背景樣式,不要用 <html>。

💡 註:user agents 指的是瀏覽器的預設樣式,在 W3C 規範中有時會縮寫成 UAs

詳細的流程可以整理成這樣:

  • html 只要有設定背景色 → 一律將html的背景色傳遞到 Cavas。
  • html 沒有設定背景色 & body 有設置背景色 → 將背景色傳遞給 html → 再傳遞到 Cavas。
  • html 沒有設定背景色 & body 沒有設置背景色 → Cavas 預設呈現白色。

看的有點亂吧,讓我們講白話一點:

只要沒有設定 <html> 的 background-image 跟 background-color,那就一律採用 <body> 的設定。

關於網頁中的滾軸

接著來討論第二個問題:為什麼內容超出 <body> 時,仍會產生滾軸?

See the Pen html&body-scrollbar by jim (@jubeatt) on CodePen.


照理來說,每個元素的 overflow 預設值都是 visible,怎麼還會產生滾軸?

其實這個道理跟剛剛背景色的傳遞行為一樣,讓我們再來看一段規範:

UAs must apply the overflow-_ values set on the root element to the viewport. However, when the root element is an [HTML] html element (including XML syntax for HTML) whose overflow value is visible (in both axes), and that element has a body element as a child, user agents must instead apply the overflow-_ values of the first such child element to the viewport. The element from which the value is propagated must then have a used overflow value of visible.

大意是指:

瀏覽器必須套用根元素 <html> 的 overflow 值傳遞給 viewport(視窗),但如果根元素 <html> 的 overflow 值為 visible,則瀏覽器必須以 <body> 的 overflow 值傳遞給 viewport(視窗)。

💡 註:user agents 指的是瀏覽器的預設樣式,在 W3C 規範中有時會縮寫成 UAs

所以在規範中我們可以先釐清兩件事情:

  • 瀏覽器產生的滾軸是由 viewport(視窗) 來控制的,不是 <html><body>
  • viewport 本身無法設定 overflow ,必須藉由 <html><body>來傳遞。

再把規範中的內容整理一下:

  • <html> 設定 visible 以外的值時,一律將這個值傳給 viewport。
  • <html> 設定 visible 時,先將<body>的 overflow 值傳給<html>,再傳遞給 viewport。

除此之外,規範中還提到了這一段:

If visible is applied to the viewport, it must be interpreted as auto. If clip is applied to the viewport, it must be interpreted as hidden.

意思是指:

  • 如果 viewport 套用的 overflow 值為 visible,則會被解譯為 auto
  • 如果 viewport 套用的 overflow 值為 clip,則會被解譯為 hidden

所以讓我們來看看,在預設值的情況下,滾軸是怎麼產生的。

1
2
3
4
html {
// 預設值
overflow: visible;
}

<html> 的值為 visible,所以參考 <body> 的 overflow。

1
2
3
4
body {
// 預設值
overflow: visible;
}

viewport 最後得到的值。

1
2
3
Viewport {
overflow: visible;
}

viewport 經過解譯後的值。

1
2
3
Viewport {
overflow: auto;
}

這就是為什麼,即便你明明沒有做任何設定,在預設值 overflow: visible 的情況下,卻還是能夠產生出滾軸的原因。

CSS 之 inline-block 中的魔法間距 CSS 之我的 scrollbar 比較好看
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×