Chrome 99新特性:@layers 規(guī)則淺析
背景CSS 寫多了,就會(huì)覺得它不太好用,經(jīng)常會(huì)遇到各種問題... 比如:
「引入順序?qū)е碌臉邮礁?jìng)爭(zhēng)問題」
用過 ant design 等組件庫(kù) + 發(fā)布在 npm 上的業(yè)務(wù)組件 的同學(xué),可能會(huì)經(jīng)常遇到自定義樣式不生效的問題,比如像這樣...
/* main.module.css */
.green {
color: green;
}
// main.tsx
import { Button } from antd ;
import antd/dist/antd.css ;
// @ts-ignore
import styles from ./main.module.css ;
import Nothing from packed-bad-component ;
export default function Component() {
const { green } = styles;
return (
<>
<Button className={green}>我是什么顏色?</Button>
<Nothing />
</>
);
}
// bad-component.tsx, published as packed-bad-component
import { Button } from antd ;
import antd/dist/antd.css?uncached ;
export default function Nothing() {
return null;
}
按鈕中的文字是什么顏色? 可以點(diǎn)擊這里試試:https://codesandbox.io/embed/bad-case-css-priority-xs3ds?fontsize=14&hidenavigation=1&theme=dark
這是因?yàn)?,如果發(fā)布的組件引入了 ant design 的樣式,就會(huì)被打包??,導(dǎo)致最終在我們的項(xiàng)目中,樣式被重復(fù)引入。而由組件引入的樣式優(yōu)先級(jí)有可能高于我們自定義的樣式,因此顯示為黑色。
導(dǎo)致問題變得更糟的是,如果此時(shí)觸發(fā)了 hot-reload,因?yàn)榇虬鼧邮綗o需重復(fù)插入,而我們的樣式有可能被更新,就會(huì)導(dǎo)致字變成綠色,進(jìn)而導(dǎo)致一些樣式問題在開發(fā)階段難以被發(fā)現(xiàn)。
「組件嵌套導(dǎo)致的樣式競(jìng)爭(zhēng)問題」
有時(shí)候,尤其是在組件中,我們可能不會(huì)隨機(jī)命名樣式,而是將一些類型的元素固定為同一個(gè)名稱,比如 .link,以方便用戶在使用我們的組件時(shí)覆蓋這些樣式。當(dāng)引入樣式較多時(shí),容易發(fā)生混亂。
┌──────────────┐
│ POST.post │
│ │
│ a.link │
│ │
│ ┌─────┐ │
│ │ .card │ │
│ │ a.link │ │
│ └─────┘ │
│ │
└──────────────┘
倘若這兩個(gè)組件有以下樣式
.post a.link { /* b = 2, c = 1 */
color: red;
}
.card .link { /* b = 2, c = 0 */
color: green;
}
這樣就會(huì)出現(xiàn) .card 中的 link 樣式被 .post 中的 link 樣式覆蓋的問題,不符合預(yù)期
目前可能會(huì)比較常見使用 BEM(Block, Element, Modifier) 的方式通過避免名稱沖突,來解決這些問題,例如這樣...
有沒有什么更好的辦法來解決我們的問題呢?
前置
在繼續(xù)之前,我們先復(fù)習(xí)一下 CSS 的樣式優(yōu)先級(jí)。
Cascading SpecificityCSS 根據(jù)定義來源不同分為 3 種:
- 用戶代理定義(user-agent declarations)
- 網(wǎng)頁作者定義(author declarations)
- 用戶定義(user declarations)
以及 2 種特殊的來源:
- 過渡定義(transition declarations)
- 動(dòng)畫定義(animation declarations)
并根據(jù)特定的順序進(jìn)行層疊,以計(jì)算出元素最終樣式。根據(jù)來源的優(yōu)先級(jí)如下(優(yōu)先級(jí)高優(yōu)先):
Selector Specificity
如果定義來源相同,則考慮選擇器權(quán)重:
特殊例子:
「:is(div, #root)」 的權(quán)重是 (a = 1, b = 0, c = 0),因?yàn)槠渲袡?quán)重最高的是 #root (1, 0, 0)
「.link:where(a, #root)」 的權(quán)重是 (a = 0, b = 1, c = 0),因?yàn)?where 中的權(quán)重是 0
「:nth-child(even of li, .item)」 的權(quán)重是 (a = 0, b = 2, c = 0),因?yàn)?:nth-child 其中權(quán)重最高的是 .item (0, 1, 0),:nth-child 本身是 (0, 1, 0),共 (0, 2, 0)
比較的時(shí)候,先是否為內(nèi)聯(lián)樣式,然后 A,然后 B,然后 C。權(quán)重還相同的樣式,則后定義的優(yōu)先級(jí)更高。
?? Web 標(biāo)準(zhǔn)似乎是不支持權(quán)重進(jìn)位的,因此,再具體的 class selector 都沒有 id selector 優(yōu)先,真實(shí)的瀏覽器實(shí)現(xiàn)是否如此呢?
@layer
背景中的兩個(gè)問題,都是因?yàn)檫x擇器權(quán)重導(dǎo)致的(比如問題 1,由定義順序?qū)е?問題 2,由隱式權(quán)重導(dǎo)致)。而層疊樣式中的用戶代理、用戶、網(wǎng)頁作者什么什么的,我好像都沒聽說過,它們沒有被充分利用起來。那么,是不是可以在計(jì)算選擇器權(quán)重前,增加點(diǎn)什么,讓它比選擇器權(quán)重更優(yōu)先計(jì)算,從而解決選擇器權(quán)重導(dǎo)致的問題呢?
即將推出的 CSS Cascading and Inheritance: Cascade Layers 致力于通過將 CSS 分層的方式避免預(yù)期外的樣式覆蓋,并提供更好的 CSS 組織結(jié)構(gòu)。通過分層,我們可以更顯式地聲明每一層的選擇器權(quán)重,確保不會(huì)出現(xiàn)默認(rèn)權(quán)重導(dǎo)致的跨層樣式覆蓋。
一句話概括 Layer 的特點(diǎn):「對(duì)于處在不同層中的樣式,無視樣式本身的權(quán)重,后聲明的層中的樣式優(yōu)先級(jí)更高,不在層中的樣式優(yōu)先級(jí)最高」。
不使用 layer 的樣式假如我們的樣式有 3 個(gè)來源,base,typography 和 utilities,它們分別設(shè)置了不同的樣式如圖。那么根據(jù)我們的選擇器權(quán)重理論:
- 第一行,命中 2 個(gè),顏色沖突,特異性相同,后聲明樣式優(yōu)先,加粗綠色
- 第二行,命中 3 個(gè),顏色沖突,.link 特異性高優(yōu)先,加粗藍(lán)色
- 第二行,命中 4 個(gè),顏色沖突,.link, .pink 特異性高優(yōu)先,.pink 后聲明優(yōu)先,加粗粉色
添加 layer 后的樣式如果我們按照不同的來源將樣式分層,會(huì)發(fā)現(xiàn) .link 變?yōu)榱司G色...
- 第一行,命中 2 個(gè),顏色屬性有沖突,后聲明的 Layer 「typography」 優(yōu)先,加粗綠色
- 第二行,命中 3 個(gè),顏色屬性有沖突,后聲明的 Layer 「typography」 優(yōu)先,加粗綠色
- 第二行,命中 4 個(gè),顏色屬性有沖突,后聲明的 Layer 「utilities」 優(yōu)先,加粗粉色
即,不管樣式選擇器的特異性(權(quán)重)如何,總是后聲明的 Layer 中的樣式更優(yōu)先一些
調(diào)整 layer 的順序假如我們對(duì)這些樣式的優(yōu)先級(jí)不滿意,想要稍做修改,只需要在前面加上聲明...
現(xiàn)在 base 的樣式優(yōu)先級(jí)更高,因此前兩行的顏色都發(fā)生了變化。
可以在這里實(shí)踐一下:https://codesandbox.io/s/chrome-99-css-cascade-layers-krgo6\ 注意瀏覽器是否支持這一特性,可以在下方 「可用性」 中對(duì)照。
其他用法
「擴(kuò)展已有的層」:
同名的 Layer 會(huì)自動(dòng)擴(kuò)展,類似 TypeScript 的 Interface,如
;
{}
{
a {
font-weight: 800;
color: red; /* ignored */
}
}
{
.link {
color: blue; /* ignored */
}
}
「組織通過 @import 導(dǎo)入的 CSS」:
我們可以將層的聲明寫在文件最上方,然后將不同功能的 css 引入并放入對(duì)應(yīng)層中,防止互相干擾。* @import 的性能遠(yuǎn)不如 ,但 link 導(dǎo)入的樣式表暫不支持 layer,web 正在尋求解決方案。
theme, layouts, components, utilities;,
/* Base */
import '../styles/base/normalize.css' layer(base); /* normalize or rest file */
import '../styles/base/base.css' layer(base); /* body and base styles */
import '../styles/base/theme.css' layer(theme); /* theme variables */
import '../styles/base/typography.css' layer(theme); /* theme typography */
import '../styles/base/utilities.css' layer(utilities); /* base utilities */
/* Layouts */
import '../styles/components/post.css' layer(layouts); /* post layout */
/* Components */
import '../styles/components/cards.css' layer(components); /* imports card */
import '../styles/components/footer.css' layer(components); /* footer component */
「聲明嵌套或匿名的層」:
層也可以被嵌套,嵌套的層也可以通過 . 來擴(kuò)展。匿名層無法擴(kuò)展。
{
{
}
}
.layout {
p {
margin-block: 1rem;
}
}
{
p {
margin-block: 1rem;
}
}
優(yōu)先級(jí)
如果層中包含嵌套層,則對(duì)每一個(gè)嵌套層
注意, !important 是反著來的,和其他層疊權(quán)重一樣
解決問題
「問題 1:引入組件順序?qū)е碌膯栴}」
因?yàn)閷又械臉邮絻?yōu)先級(jí)總是更低,因此將 antd 的樣式放入 antd 層中即可,無論以何順序引入都不會(huì)覆蓋我們不在層中的樣式。
「問題 2,組件嵌套導(dǎo)致的問題」
給來自不同組件的樣式分配不同的層,通過組織層的順序,即可避免這一問題。
注意事項(xiàng)
不是創(chuàng)建作用域的手段它只是一個(gè)組織 CSS、避免選擇器權(quán)重導(dǎo)致問題的方式,不是創(chuàng)建 CSS 作用域的方式。如果需要限制 CSS 的作用域,還是得添加更具體的樣式,如 .card:
.card a {
/* ... */
}
層疊層中的 CSS 優(yōu)先級(jí)低于不在層中的 CSS
層疊層中的 CSS 優(yōu)先級(jí)更低,是考慮到這樣在已有的代碼中引入層疊層,會(huì)更容易一些,不太會(huì)帶來很大的問題。
!important 的層疊權(quán)重相反如果存在 !important,則
- 先聲明的層中的樣式優(yōu)先級(jí)高
- 層中的樣式優(yōu)先級(jí)高
- 不在層中的樣式優(yōu)先級(jí)低
這樣和原有的層疊權(quán)重比較一致。
明確插入
點(diǎn)層疊層的層疊權(quán)重與對(duì)應(yīng)層疊層在代碼中第一次出現(xiàn)的順序有關(guān)系,因此,最好把可能用到的層放在最頂點(diǎn),可以很清晰地看到層疊層的順序。
注意權(quán)重
引入了層疊層之后,可能會(huì)出現(xiàn)選擇器權(quán)重更高,卻被權(quán)重更低的樣式覆蓋的情況,提高權(quán)重又不能解決這個(gè)問題。當(dāng)出現(xiàn)這種情況時(shí),就要考慮是不是因?yàn)閷盈B層導(dǎo)致的... 后聲明的層疊層,層疊權(quán)重更高,可以無視選擇器權(quán)重覆蓋其他樣式。
引入層后,權(quán)重發(fā)生了一些變化,但一定要注意,只有同一等級(jí)才能對(duì)比,因此不要搞錯(cuò)了比較順序...
參考
- 「Chrome Blog」: https://developer.chrome.com/blog/cascade-layers/
- 「@layer 作者的想法」: https://css.oddbird.net/layers/explainer/
- 「MDN」: https://developer.mozilla.org/en-US/docs/Web/CSS/
- 「@layerW3 中的文檔」: https://w3.org/TR/css-cascade-5/#layering