冷知識(shí)!使用 Display: Contents 實(shí)現(xiàn)幽靈節(jié)點(diǎn)?
??display: contents?
? 是一個(gè)比較陌生的屬性,雖然屬于 display 這個(gè)基本上是最常見(jiàn)的 CSS 屬性,但是 ??contents?
? 這個(gè)取值基本不會(huì)用到。但是它早在 2016 年就已經(jīng)得到了 Firefox 的支持。
本文將深入一下這個(gè)有意思的屬性值。
基本用法
根據(jù) W3C[1] 對(duì) ??display: contents?
? 的定義。
The element itself does not generate any boxes, but its children and pseudo-elements still generate boxes and text runs as normal. For the purposes of box generation and layout, the element must be treated as if it had been replaced in the element tree by its contents (including both its source-document children and its pseudo-elements, such as ::before and ::after pseudo-elements, which are generated before/after the element’s children as normal).
簡(jiǎn)單翻譯一下即是,設(shè)置了該屬性值的元素本身將不會(huì)產(chǎn)生任何盒子,但是它保留其子代元素的正常展示。
看個(gè)簡(jiǎn)單的例子。有如下簡(jiǎn)單三層結(jié)構(gòu):
<div class="container">
<div class="wrap">
<div class="inner"></div>
</div>
</div>
簡(jiǎn)單的 CSS 如下:
.container {
width: 200px;
height: 200px;
background: #bbb;
}
.wrap {
border: 2px solid red;
padding: 20px;
box-sizing: border-box;
}
.inner {
border: 2px solid green;
padding: 20px;
box-sizing: border-box;
}
表現(xiàn)如下:
這個(gè)非常好理解,但是如果,我們給中間層的容器添加上 ??display: contents?
?,再看看效果:
<div class="container">
<div class="wrap" style="display: contents">
<div class="inner"></div>
</div>
</div>
可以看到,沒(méi)有了中間層的 ??border: 2px solid red?
? 的紅色邊框,整個(gè) ??.wrap?
? div 好像不存在一樣,但是它的子元素卻是正常的渲染了。
重點(diǎn),設(shè)置了???display: contents?
?的元素本身不會(huì)被渲染,但是其子元素能夠正常被渲染。
這個(gè)屬性我一直在思考有什么非常適合的使用點(diǎn)。
總結(jié)來(lái)說(shuō),這個(gè)屬性適用于那些充當(dāng)遮罩(wrapper)的元素,這些元素本身沒(méi)有什么作用,可以被忽略的一些布局場(chǎng)景。也就是幽靈 DOM 節(jié)點(diǎn)。
充當(dāng)無(wú)語(yǔ)義的包裹框
最近寫(xiě) React、Vue 的時(shí)候,發(fā)現(xiàn)這個(gè)屬性在寫(xiě) JSX 的時(shí)候能有很好的作用,并且也非常符合這個(gè)屬性本身的定位。
我們?cè)趯?xiě) DOM 結(jié)構(gòu)時(shí),經(jīng)常需要輸出一段模板,或者需要一些無(wú)語(yǔ)義的幽靈節(jié)點(diǎn)。
return (
<div class="wrap">
<h2>Title</h2>
<div>...</div>
</div>
)
我們只是想輸出 ??.wrap?
? div 內(nèi)的內(nèi)容,但是由于框架要求,輸出的 JSX 模板必須包含在一個(gè)父元素之下,所以不得已,需要添加一個(gè) ??.wrap?
? 進(jìn)行包裹,但是這個(gè) ??.wrap?
? 本身是沒(méi)有任何樣式的。
如果輸出的元素是要放在其他 ??display: flex?
?、??display: grid?
? 容器之下,加了一層無(wú)意義的 ??.wrap?
? 之后,整個(gè)布局又需要重新進(jìn)行調(diào)整,麻煩。
一種方法是使用框架提供的容器 ??<React.Fragment>?
?,它不會(huì)向頁(yè)面插入任何多余節(jié)點(diǎn)。
在 Vue 中類(lèi)似的是 ?
?<template>?
? 元素, ??<template>?
? 也是不會(huì)被渲染在 DOM 樹(shù)中,查看頁(yè)面結(jié)構(gòu)也無(wú)法看到,但是 ??display: contents?
? 是存在于頁(yè)面結(jié)構(gòu)中的,只是沒(méi)有生成任何盒子。
這個(gè)多出來(lái)的父元素其實(shí)是沒(méi)必要的。這個(gè)時(shí)候,我們也可以添加上 ??display: contents?
?,像是這樣:
return (
<div class="wrap" style="display: contents">
<h2>Title</h2>
<div>...</div>
</div>
)
這樣,它既起到了包裹的作用,但是在實(shí)際渲染中,這個(gè) div 其實(shí)沒(méi)有生成任何盒子,一舉兩得。并且像一些 ??flex?
? 布局、??grid?
? 布局,也不會(huì)受到影響。
Codepen Demo -- display: contents | display: flex 的穿透影響[2]。
讓代碼更加符合語(yǔ)義化
考慮這個(gè)非常實(shí)際的場(chǎng)景,現(xiàn)在我們的頁(yè)面上充斥了大量的可點(diǎn)擊按鈕,或者點(diǎn)擊觸發(fā)相應(yīng)功能的文字等元素。但是,從語(yǔ)義上而言,它們應(yīng)該是一個(gè)一個(gè)的 ??<button>?
?,但是實(shí)際上,更多時(shí)候我們都是使用了 ??<p>、<div>、<a>?
? 等標(biāo)簽進(jìn)行了模擬,給他們加上了相應(yīng)的點(diǎn)擊事情而已。
像是下面這樣,雖然沒(méi)什么問(wèn)題,但是相對(duì)而言不那么符合語(yǔ)義化:
<p class="button">
Button
</p>
<p class="button">
Click Me
</p>
.button {
width: 120px;
line-height: 64px;
text-align: center;
background-color: #ddd;
border: 2px solid #666;
}
我們不使用 ??<button>?
? 的原因有很多,??<button>?
? 相對(duì) div 而言沒(méi)那么好控制,且會(huì)引入很多默認(rèn)樣式。但是,有了 ??display: contents?
?,我們可以讓我們的代碼既符合語(yǔ)義化,同時(shí)不需要去解決 ??<button>?
? 帶來(lái)的一些樣式問(wèn)題:
<p class="button">
<button style="display: contents">
Button
</button>
</p>
<p class="button">
<button style="display: contents">
Click Me
</button>
</p>
添加了 ??<button style="display: contents">Click Me</button>?
? 的包裹,不會(huì)對(duì)樣式帶來(lái)什么影響,button 也不會(huì)實(shí)際渲染在頁(yè)面結(jié)構(gòu)中,但是頁(yè)面的結(jié)構(gòu)語(yǔ)義上好了不少。
CodePen Demo -- Button with display: contents[3]。
對(duì)于對(duì)頁(yè)面結(jié)構(gòu)、語(yǔ)義化有強(qiáng)迫癥的一些同學(xué)而言,靈活運(yùn)用這個(gè)屬性可以解決很多問(wèn)題。
當(dāng)然,對(duì)于提升使用 div 、a 標(biāo)簽?zāi)M的按鈕的可訪問(wèn)性而言,更好的辦法是是通過(guò) WAI-ARIA 標(biāo)準(zhǔn)[4]定義的一系列 ?
?ARIA-*?
? 屬性來(lái)改善,具體的相關(guān)內(nèi)容可以看看這里 -- 前端優(yōu)秀實(shí)踐不完全指南[5]。
在替換元素及表單元素中一些有意思的現(xiàn)象
??display: contents?
? 并非在所有元素下的表現(xiàn)都一致。
對(duì)于可替換元素及大部分表單元素,使用 ??display: contents?
? 的作用類(lèi)似于 ??display: none?
?。
也就是說(shuō)對(duì)于一些常見(jiàn)的可替換元素、表單元素:
- ?
?<br>?
? - ?
?<canvas>?
? - ?
?<object>?
? - ?
?<audio>?
? - ?
?<iframe>?
? - ?
?<img>?
? - ?
?<video>?
? - ?
?<frame>?
? - ?
?<input>?
? - ?
?<textarea>?
? - ?
?<select>?
?
作用了 ??display: contents?
? 相當(dāng)于使用了 ??display: none?
? ,元素的整個(gè)框和內(nèi)容都沒(méi)有繪制在頁(yè)面上。
<button> 的一些異同
與其他表單元素不一樣,正常而言,添加了 ??display: contents?
? 相當(dāng)于被隱藏,不會(huì)被渲染。但是實(shí)際運(yùn)用過(guò)程中發(fā)現(xiàn),??<button></button>?
? 如果包裹了內(nèi)容,其一些可繼承樣式還是會(huì)被子內(nèi)容繼承。這個(gè)實(shí)際使用的過(guò)程中需要注意一下。
對(duì) A11Y 的影響
在一些外文文檔中有一些討論是關(guān)于 ??display: contents?
? 的使用會(huì)影響到頁(yè)面的可訪問(wèn)性。例如作用了 ??display: contents?
? 的容器及列表,會(huì)對(duì)頁(yè)面的可訪問(wèn)性帶來(lái)一些意外結(jié)果。
- [css-a11y][css-display] display: contents; strips semantic role from elements[6]。
這個(gè)我看暫時(shí)沒(méi)有明確的結(jié)論,如果你的頁(yè)面對(duì)可訪問(wèn)性的要求很高,具體使用的此屬性的話也是需要注意一下這一點(diǎn)。
CSS 中類(lèi)似的一些影響布局的屬性
CSS 本身其實(shí)也在一直在努力,增加了各種屬性去讓我們?cè)诓季稚嫌懈嗟目臻g與控制權(quán)??偠灾o我的感受是讓 CSS 更加的像是一個(gè)完整的工程而不僅僅只是展現(xiàn)樣式。
類(lèi)似的一些有意思的屬性:
- CSS新特性contain,控制頁(yè)面的重繪與重排 [7]。
CAN I USE
看看兼容性[8](2022-05-31)。
display: contents 兼容性
到今天,兼容性已經(jīng)不算太慘淡,如果不考慮 IE 系列,可以用起來(lái)了。當(dāng)然,如果求穩(wěn),保守起見(jiàn),可以考慮用在一些漸進(jìn)增強(qiáng)的場(chǎng)景當(dāng)中。
參考
- How display: contents; Works[9]。
- CSS的display:contents[10]。
- Display: Contents Is Not a CSS Reset[11]。
最后
好了,本文到此結(jié)束,希望對(duì)你有幫助 :)
參考資料
[1]W3C: https://developer.mozilla.org/zh-CN/docs/Web/CSS/display。
[2]Codepen Demo -- display: contents | display: flex 的穿透影響: https://codepen.io/Chokcoco/pen/wvKLBVV。
[3]CodePen Demo -- Button with display: contents: https://codepen.io/Chokcoco/pen/oNjRePd。
[4]WAI-ARIA 標(biāo)準(zhǔn): https://www.w3.org/TR/wai-aria-1.1/。
[5]前端優(yōu)秀實(shí)踐不完全指南: https://github.com/chokcoco/cnblogsArticle/issues/26。
[6][css-a11y][css-display] display: contents; strips semantic role from elements: https://github.com/w3c/csswg-drafts/issues/3040。
[7]CSS新特性contain,控制頁(yè)面的重繪與重排 : https://github.com/chokcoco/iCSS/issues/23。
[8]兼容性: https://caniuse.com/#search=display%3A%20contents。
[9]How display: contents; Works: https://bitsofco.de/how-display-contents-works/。
[10]CSS的display:contents: https://www.w3cplus.com/css/display-contents-is-coming.html。
[11]Display: Contents Is Not a CSS Reset: https://adrianroselli.com/2018/05/display-contents-is-not-a-css-reset.html。