前端開(kāi)發(fā)如何更好的避免樣式?jīng)_突?級(jí)聯(lián)層(CSS@layer)
一、什么是級(jí)聯(lián)層(Cascade Layers)?
1.1 級(jí)聯(lián)層的官方定義
我們參看Cascading and Inheritance Level 5(13 January 2022) 中6.4節(jié)所述:
級(jí)聯(lián)層提供了一種結(jié)構(gòu)化的方式來(lái)組織和平衡單個(gè)起源中的問(wèn)題。單個(gè)級(jí)聯(lián)層內(nèi)的規(guī)則級(jí)聯(lián)在一起,不與層外的樣式規(guī)則交錯(cuò)。
開(kāi)發(fā)者可以創(chuàng)建層來(lái)表現(xiàn)元素默認(rèn)值、第三方庫(kù)、主題、組件、覆蓋和其他樣式問(wèn)題,并且能夠以顯式方式重新排序?qū)蛹?jí),而無(wú)需更改每個(gè)層內(nèi)的選擇器或特異性,或依賴源順序來(lái)解決跨層的沖突。
單純看官方定義和概括可能比較晦澀,下面我們會(huì)結(jié)合案例來(lái)說(shuō)清楚。
1.2 級(jí)聯(lián)層為了解決什么問(wèn)題?
簡(jiǎn)而言之:級(jí)聯(lián)層的出現(xiàn)就是為了使 CSS 開(kāi)發(fā)者可以更簡(jiǎn)單直接地控制級(jí)聯(lián)。
我們來(lái)假設(shè)日常開(kāi)發(fā)中的一個(gè)場(chǎng)景,從場(chǎng)景去理解級(jí)聯(lián)層在解決什么問(wèn)題。
如下圖:
我們?cè)瓉?lái)的'display'文案是紅色,當(dāng)我們引入了一個(gè)第三方組件庫(kù),第三方庫(kù)中有以下樣式。
/* 開(kāi)發(fā)者樣式 */
.item {
color: red;
}
/* 第三方庫(kù) */
#app .item {
color: green;
border: 5px solid green;
font-size: 1.3em;
padding: 0.5em;
width: 120px;
}
就會(huì)導(dǎo)致'display'文案變成綠色。
我們想要將'display'文案的顏色由綠色改成紅色一般的手段是增加選擇器特異性(優(yōu)先級(jí))。比如在開(kāi)發(fā)頁(yè)面中對(duì)開(kāi)發(fā)者樣式進(jìn)行修改:
/* 開(kāi)發(fā)者樣式 */
#app div.item {
color: red;
}
/* 第三方庫(kù) */
#app .item {
color: green;
border: 5px solid green;
font-size: 1.3em;
padding: 0.5em;
width: 120px;
}
或者借助級(jí)聯(lián)中出場(chǎng)順序?qū)?yōu)先級(jí)的影響在用戶頁(yè)面中重寫
/* 第三方庫(kù) */
#app .item {
color: green;
border: 5px solid green;
font-size: 1.3em;
padding: 0.5em;
width: 120px;
}
/* 開(kāi)發(fā)者樣式 */
#app .item {
color: red;
}
效果如下:
再舉個(gè)例子:
比如有可能第三方組件寫了
a { color: blue; }
那項(xiàng)目開(kāi)發(fā)中由于引入這個(gè)第三方組件 就會(huì)導(dǎo)致樣式污染,因?yàn)榈谌綆?kù)的樣式往往都在項(xiàng)目設(shè)置的通用樣式比如common.css后加載。
如果后面想在代碼中覆蓋某些屬性,使用高特異性選擇器的語(yǔ)句就可能會(huì)導(dǎo)致問(wèn)題。然后因?yàn)橛袉?wèn)題就會(huì)選擇更高特異性的擇器的語(yǔ)句或使用!important,這會(huì)使代碼變得冗長(zhǎng)也可能會(huì)帶來(lái)副作用。低特異性選擇器的語(yǔ)句很容易被后面出現(xiàn)在代碼中的語(yǔ)句覆蓋。在自己的代碼之后加載第三方 CSS 時(shí)特別會(huì)出現(xiàn)這種問(wèn)題。
所以級(jí)聯(lián)層就是為了解決以上場(chǎng)景出現(xiàn)的,級(jí)聯(lián)層在級(jí)聯(lián)中的的位置是在內(nèi)聯(lián)樣式和選擇器特異性之間。當(dāng)有些css聲明就是設(shè)置要低優(yōu)先級(jí)且不受選擇器特異性影響那么使用級(jí)聯(lián)層再合適不過(guò)。
運(yùn)用級(jí)聯(lián)層解決第一個(gè)日常開(kāi)發(fā)場(chǎng)景痛點(diǎn)的css代碼如下:
/* 排序?qū)?*/
@layer reset, lib;
/* 通用樣式 */
@layer reset {
#app .item {
color: black;
width: 100px;
padding: 1em;
}
}
/* 第三方庫(kù)樣式 */
/*我們將第三方庫(kù)的樣式全部放到lib層*/
/*這里一般使用@import導(dǎo)入的方式*/
/*為了示例簡(jiǎn)單我們簡(jiǎn)化了操作*/
@layer lib {
#app .item {
color: green;
border: 5px solid green;
font-size: 1.3em;
width: 130px;
}
}
/* 開(kāi)發(fā)者樣式 */
.item {
color: red;
}
為了知道為什么上面的css代碼能解決沖突問(wèn)題,更好地理解級(jí)聯(lián)層的作用,理解一些現(xiàn)象背后的根因,了解級(jí)聯(lián)層和級(jí)聯(lián)的關(guān)系,我們繼續(xù)往下看。
二、理解級(jí)聯(lián)層的前提——級(jí)聯(lián)(cascade)
2.1 什么是級(jí)聯(lián)?
CSS中有兩個(gè)重要的基礎(chǔ)規(guī)則,一個(gè)是繼承,一個(gè)是級(jí)聯(lián)。
繼承
指的是類似 color,font-family,font-size,line-height 等屬性父元素設(shè)置后,子元素會(huì)繼承的特性。
級(jí)聯(lián)
可以簡(jiǎn)單理解為是CSS 用來(lái)解決要應(yīng)用于元素的具體樣式的算法。也就是基于一些優(yōu)先級(jí)排序輸出給給定元素上屬性值一個(gè)級(jí)聯(lián)值。級(jí)聯(lián)值是級(jí)聯(lián)的結(jié)果。
2.2 當(dāng)前級(jí)聯(lián)的排序標(biāo)準(zhǔn)
我們參看Cascading and Inheritance Level 5(13 January 2022) 中6.1節(jié),
相比于Cascading and Inheritance Level 4(14 January 2016) 中的定義有明顯變化。
最重要的變化就是增加了級(jí)聯(lián)層。由此級(jí)聯(lián)排序變成:
- 起源和重要性(Origin and Importance)
- 上下文(Context)
- 樣式屬性(Element-Attached Styles)
- 層(Layers)
- 特異性(Specificity)
- 出場(chǎng)順序(又名源代碼順序)(Order of Appearance)
瀏覽器在確定最終元素樣式呈現(xiàn)的時(shí)候,會(huì)依據(jù)這些準(zhǔn)則按照優(yōu)先權(quán)從高到低排序,并且會(huì)一個(gè)一個(gè)的檢查,直到確定最終樣式。
2.3 級(jí)聯(lián)起源(Cascading Origins)
2.3.1 三個(gè)核心起源
css中每個(gè)樣式規(guī)則有三個(gè)核心起源,它決定了它進(jìn)入級(jí)聯(lián)的位置,理解起源優(yōu)先級(jí)是理解級(jí)聯(lián)的關(guān)鍵。
- 用戶代理來(lái)源(瀏覽器內(nèi)置樣式)
- 用戶來(lái)源(瀏覽器的用戶設(shè)置 )
- 作者來(lái)源(Web開(kāi)發(fā)者)
2.3.2 起源的優(yōu)先級(jí)
Css聲明的起源取決于它來(lái)自哪里,重要性在于它是否用!important聲明。
各種起源的優(yōu)先級(jí)按降序排列:
- 過(guò)渡
- 重要的用戶代理
- 重要用戶
- 重要作者
- 動(dòng)畫
- 普通作者
- 普通用戶
- 普通用戶代理
越靠前來(lái)源的聲明優(yōu)先級(jí)越高。過(guò)渡和動(dòng)畫我們可以暫時(shí)忽略。
2.4 熟悉又陌生的 !important
通常作為開(kāi)發(fā)者,!important會(huì)被我們視為一種增加特異性的方法,用以覆蓋內(nèi)聯(lián)樣式或特異性較高的選擇器。
但是!important設(shè)計(jì)出來(lái)的初衷是作為整體級(jí)聯(lián)中的一個(gè)特性,來(lái)平衡開(kāi)發(fā)者、用戶設(shè)置和瀏覽器內(nèi)置之間對(duì)css優(yōu)先級(jí)的影響能力。
默認(rèn)情況下三者的優(yōu)先級(jí)是:作者來(lái)源> 用戶來(lái)源>用戶代理來(lái)源(可以參看上文起源優(yōu)先級(jí)中6-8的排序)。但是當(dāng)css聲明添加!important之后會(huì)使它們的優(yōu)先順序顛倒(參看上文起源優(yōu)先級(jí)中2-4的排序)。
如果站在瀏覽器和用戶的角度看!important提供了在必要時(shí)重獲優(yōu)先級(jí)控制權(quán)的能力,而非只是簡(jiǎn)單的增加特異性。
舉個(gè)例子:
瀏覽器默認(rèn)樣式表包含我們開(kāi)發(fā)者無(wú)法覆蓋的重要樣式。
瀏覽器對(duì)具有'hidden'類型的input輸入框設(shè)置了默認(rèn)的展示屬性并且將其聲明為重要。
input[type=hidden i] { display: none !important; }
看下面兩張圖例:
第一張可以看出我們對(duì)具有'hidden'類型的input輸入框的展示屬性設(shè)置成了顯示并且聲明為重要
第二張是樣式最終計(jì)算結(jié)果:隱藏
從上面的瀏覽器表現(xiàn)可以看到我們作為開(kāi)發(fā)者在作者樣式表中設(shè)置的規(guī)則沒(méi)能覆蓋用戶代理樣式表中的相同規(guī)則。
這驗(yàn)證了上面說(shuō)的:在級(jí)聯(lián)中!important會(huì)顛倒三大核心起源默認(rèn)優(yōu)先順序。
驗(yàn)證的過(guò)程中還發(fā)現(xiàn)了一個(gè)chrome控制臺(tái)這邊的bug,看上面的第一張圖例:沒(méi)生效的規(guī)則不劃刪除線,生效的反而劃刪除線了。
再看一個(gè)官方文檔的例子加強(qiáng)一下理解:
圖片來(lái)源:w3.org
font-size的值最終是‘12pt ’。
因?yàn)樽髡邩邮奖碇刑砑?important的規(guī)則優(yōu)先權(quán)高于用戶樣式表中普通規(guī)則。
text-indent的值最終是‘1em’。
因?yàn)殡m然兩個(gè)樣式表都標(biāo)注了!important,但是標(biāo)注!important用戶聲明優(yōu)先級(jí)大于標(biāo)注!important作者聲明。
2.5 一張圖帶你理解級(jí)聯(lián)
下圖可以幫助我們直觀的理解級(jí)聯(lián)以及級(jí)聯(lián)層在級(jí)聯(lián)中的位置:
圖片來(lái)源:css-tricks
我們會(huì)發(fā)現(xiàn)平時(shí)操作最多的選擇器特異性(selector specificity)只是級(jí)聯(lián)中的一小部分。也輕松地理解了為什么內(nèi)聯(lián)樣式優(yōu)先級(jí)天然高。同時(shí)我們會(huì)發(fā)現(xiàn)!important在級(jí)聯(lián)中有特殊地位。他穿插在級(jí)聯(lián)規(guī)則的各個(gè)階段并能顛倒優(yōu)先級(jí)。
三、級(jí)聯(lián)層(CSS@layer) 使用探索
3.1 @layer 是什么?
我們來(lái)看MDN上的定義:
The @layer CSS at-rule is used to declare a cascade layer and can also be used to define the order of precedence in case of multiple cascade layers.
也就是說(shuō) @layer 這個(gè)at-rule(AT規(guī)則) 用于聲明級(jí)聯(lián)層(cascade layer),也能用于定義多個(gè)級(jí)聯(lián)層的優(yōu)先級(jí)。
At-rules 是什么?
At-rules是指導(dǎo) CSS 如何表現(xiàn)的CSS 語(yǔ)句。它們以 at 符號(hào) ' @' ( U+0040 COMMERCIAL AT) 開(kāi)頭,后跟一個(gè)標(biāo)識(shí)符,包括下一個(gè)分號(hào) ' ;' ( U+003B SEMICOLON) 或下一個(gè)CSS 塊之前的所有內(nèi)容。
我們開(kāi)發(fā)常見(jiàn)的at-rule有@charset、@media、@font-face 、@keyframes 等。
3.2 @layer的句法規(guī)則
@layer的句法如下:
@layer layer-name {rules}
@layer layer-name;
@layer layer-name, layer-name, layer-name;
@layer {rules}
3.3 如何創(chuàng)建級(jí)聯(lián)層
可以通過(guò)多種方式創(chuàng)建級(jí)聯(lián)層。
第一種方法是:創(chuàng)建命名的級(jí)聯(lián)層,其中包含該層的 CSS 規(guī)則,如下所示:
@layer green {
.item {
color: green;
border: 5px solid green;
font-size: 1.3em;
padding: 0.5em;
width: 120px;
}
}
@layer special {
.item {
color: rebeccapurple;
}
}
第二種方法是:創(chuàng)建一個(gè)命名的級(jí)聯(lián)層而不分配任何樣式。這可以是單層,如下所示:
@layer green;
可以一次定義多個(gè)層,如下:
@layer green, special
一次定義多個(gè)層有什么好處呢?
因?yàn)?strong>聲明層的初始順序決定了層的優(yōu)先級(jí)。與聲明一樣,如果在多個(gè)層中找到聲明,最后定義的層聲明將最終生效。看下面代碼:
@layer green,special;
@layer green {
#app .item {
color: green;
border: 5px solid green;
font-size: 1.3em;
padding: 0.5em;
width: 120px;
}
}
@layer special {
.item {
color: rebeccapurple;
}
}
效果如下圖:
special層中item樣式規(guī)則將被應(yīng)用即使它的特異性低于 green層中的規(guī)則。這是因?yàn)?strong>一旦層順序定義完成,就會(huì)忽略選擇器特異性。
同樣也會(huì)忽略出現(xiàn)的順序:
我們聲明層名稱并設(shè)置它們的順序后,可以通過(guò)重新聲明名稱來(lái)將 CSS 規(guī)則添加到圖層。然后將樣式附加到層,并且層順序不會(huì)更改。比如如下代碼雖然@layer green重新聲明了并在文件后方但是由于順序一開(kāi)始已經(jīng)設(shè)置所以字體顏色還是紫色:
@layer green,special;
@layer special {
.item {
color: rebeccapurple;
}
}
@layer green {
.item {
color: green;
border: 5px solid green;
font-size: 1.3em;
padding: 0.5em;
width: 120px;
}
}
效果如下:
忽略選擇器特異性和代碼出現(xiàn)順序可以讓我們創(chuàng)建更簡(jiǎn)單的 CSS 選擇器,并使代碼優(yōu)雅,因?yàn)椴槐卮_保選擇器具有足夠高的特異性來(lái)覆蓋其他css規(guī)則,只需要確保它出現(xiàn)在后面的層中。
第三種方法是:創(chuàng)建一個(gè)沒(méi)有名稱的級(jí)聯(lián)層。例如:
@layer {
.item {
color: black;
}
}
這將創(chuàng)建一個(gè)匿名級(jí)聯(lián)層,該層功能與命名層相同。但是使用匿名層有如下缺點(diǎn):
- 可讀性較差:匿名層沒(méi)有名稱,會(huì)導(dǎo)致樣式表不易閱讀和理解。特別是在大型項(xiàng)目中,可能會(huì)出現(xiàn)樣式不斷增加,難以跟蹤和管理的問(wèn)題。
- 難以擴(kuò)展:如果稍后想要更改特定樣式或組合,也會(huì)很難找到特定的代碼塊。
- 不可復(fù)用性:匿名層中的樣式不能在其他地方重復(fù)使用或引用。這樣會(huì)使樣式表更難以管理和維護(hù)。
平時(shí)我們盡量避免使用匿名層。但當(dāng)我們是樣式庫(kù)的作者,并想將某些css代碼不被使用者修改可以借助匿名層做到這一點(diǎn)。
第四種方法是:使用@import。CSS 原生支持 @import 導(dǎo)入其他 CSS 文件。
@import url(index.css) layer(index);
同時(shí),也支持匿名引入,例如:
@import url(index.css) layer;
我們?cè)谑褂聾import時(shí)候需要放在除@charset之外的樣式規(guī)則前,否則無(wú)法導(dǎo)入。
可能的第五種方式仍在討論中:通過(guò)元素上的屬性。請(qǐng)參閱[css-cascade] Provide an attribute for assigning ato a cascade layer #5853。
3.4 層的嵌套規(guī)則
圖層可以嵌套。例如:
@layer base {
p { max-width: 70ch; }
}
@layer framework {
@layer base {
p { margin-block: 0.75em; }
}
@layer theme {
p { color: #222; }
}
}
生成的層可以表示為一棵樹(shù):
1.base
- framework
- base
2.theme
或可以用扁平列表表示
- base
- framework.base
- framework.theme
要將樣式附加到嵌套層,您需要使用以下全名來(lái)引用它:
@layer framework {
@layer default {
p { margin-block: 0.75em; }
}
@layer theme {
p { color: #222; }
}
}
@layer framework.theme {
/* 這些樣式會(huì)被添加到framework層里面的theme層 */
blockquote { color: rebeccapurple; }
}
看效果:
3.5 層的排序規(guī)則
級(jí)聯(lián)層按照它們聲明的順序排序。第一層優(yōu)先級(jí)最低,最后一層優(yōu)先級(jí)最高。但是,未分層的樣式具有最高優(yōu)先級(jí):
/* 未分層 */a { color: green; }
@layer layer-1 { a { color: red; } }
@layer layer-2 { a { color: orange; } }
@layer layer-3 { a { color: yellow; } }
優(yōu)先級(jí)順序如下:
- 未分層樣式
- layer-3
- layer-2
- layer-1
看下圖示例:
層可以在一個(gè)地方被定義圖層(以建立圖層順序),然后在任何地方添加樣式
/* 定義在一個(gè)地方 */
@layer my-layer;
/* 其他樣式*/
...
/* 在某個(gè)地方添加樣式 */
@layer my-layer { a { color: red; } }
3.6 加上!important之后的排序規(guī)則
/* 未分層 */ a { color: green !important; }
@layer layer-1 { a { color: red !important; } }
@layer layer-2 { a { color: orange !important; } }
@layer layer-3 { a { color: yellow !important; } }
任何加上重要聲明的樣式都以相反的順序應(yīng)用
優(yōu)先級(jí)順序如下:
- !important layer-1
- !important layer-2
- !important layer-3
- !important 未分層樣式
看下圖示例:
這里我們看到對(duì)應(yīng)規(guī)則在chrome瀏覽器的顯示是正確的。但是在開(kāi)發(fā)者控制臺(tái)中的樣式一欄規(guī)則顯示有問(wèn)題。應(yīng)該是chrome瀏覽器開(kāi)發(fā)者控制臺(tái)的bug。
3.7 嵌套層的排序規(guī)則
@layer layer-1 { a { color: red; } }
@layer layer-2 { a { color: orange; } }
@layer layer-3 {
@layer sub-layer-1 { a { color: yellow; } }
@layer sub-layer-2 { a { color: green; } }
/* 未嵌套 */ a { color: blue; }
}
/* 未分層 樣式 */ a { color: black; }
優(yōu)先級(jí)順序如下:
- 未分層 樣式
- layer-3
-layer-3 未嵌套
-layer-3 sub-layer-2
-layer-3 sub-layer-1 - layer-2
- layer-1
3.8 媒體查詢對(duì)層排序的影響
以下層順序?qū)⑷Q于匹配的媒體條件:
例如:
@media (min-width: 600px) {
@layer layout {
.item {
font-size: x-large;
}
}
}
@media (prefers-color-scheme: dark) {
@layer theme {
.item {
color: red;
}
}
}
如果兩個(gè)媒體查詢的規(guī)則中匹配一個(gè)那么對(duì)應(yīng)的級(jí)聯(lián)層生效。如果兩者都匹配,那么圖層順序?qū)閘ayout, theme都生效。如果兩個(gè)都不匹配則不定義層。下圖是兩者都匹配的場(chǎng)景。
四、現(xiàn)在就能使用級(jí)聯(lián)層嗎?
4.1 目前瀏覽器支持程度
圖片來(lái)源:developer.mozilla.org
目前所有現(xiàn)代瀏覽器均已經(jīng)支持 @layer 規(guī)則。所有瀏覽器廠商都支持的特性未來(lái)一定比較實(shí)用。
4.2 W3C 鼓勵(lì)可以作為日常使用
SS 的標(biāo)準(zhǔn)化流程由 W3C Cascading Style Sheets Working Group (CSSWG)——W3C層疊樣式列表小組以及獨(dú)立CSS專家組成。W3C 本身并不制定標(biāo)準(zhǔn),而是作為一個(gè)論壇式的平臺(tái),接收來(lái)自小組成員的提交,并通過(guò)會(huì)議來(lái)商討制定標(biāo)準(zhǔn),所有的提交以及討論都是公開(kāi)透明的,可以在 W3C 網(wǎng)站上看到會(huì)議的記錄,可以簡(jiǎn)單分為4個(gè)大階段:
- 工作草案( WD )
- 候選人推薦( CR )
- 提議的建議( PR )
- W3C 推薦( REC )
下圖可以幫助理解:
圖片來(lái)源:w3.org
W3C 通過(guò)狀態(tài)碼表示規(guī)范的成熟度。成熟度從低到高排序如下圖。
圖片來(lái)源:w3.org
再看下圖:包含layer概念的標(biāo)準(zhǔn)討論已經(jīng)到達(dá)CR階段。
圖片來(lái)源:w3.org
W3C 鼓勵(lì)從 CR階段的標(biāo)準(zhǔn) 開(kāi)始可以作為日常使用。
五、總結(jié)
最后,我們回到通過(guò)級(jí)聯(lián)層如何解決“引入了一個(gè)第三方組件庫(kù)導(dǎo)致樣式覆蓋“的問(wèn)題上。
css代碼如下:
/* 排序?qū)?*/
@layer reset, lib;
/* 通用樣式 */
@layer reset {
#app .item {
color: black;
width: 100px;
padding: 1em;
}
}
/* 第三方庫(kù)樣式 */
/*我們將第三方庫(kù)的樣式全部放到lib層,這里一般使用@import導(dǎo)入的方式,為了示例簡(jiǎn)單我們簡(jiǎn)化了操作*/
@layer lib {
#app .item {
color: green;
border: 5px solid green;
font-size: 1.3em;
width: 130px;
}
}
/* 開(kāi)發(fā)者樣式 */
.item {
color: red;
}
我們將第三方庫(kù)的樣式全部放到lib層,將需要重置的一些樣式放到reset層,自己開(kāi)發(fā)的樣式不放入層中(當(dāng)然你也可以放入到一層然后排序在最后)。由此我們實(shí)現(xiàn)了樣式的分層解決了第三方組件庫(kù)導(dǎo)致樣式覆蓋的問(wèn)題,而且做到開(kāi)發(fā)者樣式簡(jiǎn)單不冗長(zhǎng)。
效果如下:
級(jí)聯(lián)層(CSS@layer)已經(jīng)歷概念提出到到瀏覽器全面支持的階段。也許在不久的將來(lái)大家都會(huì)普遍使用它,期望本文能給大家?guī)?lái)一定幫助。