主流瀏覽器都支持原生 CSS 嵌套了!
8 月 29 日,Firefox 117版本發布,該版本增加了對 CSS 原生嵌套的支持。至此,所有主流桌面瀏覽器現在都已經支持原生 CSS 嵌套語法!
注意,有些移動瀏覽器還不支持該語法,不過,這些瀏覽器合計只占約全球瀏覽器市場份額的 3%。
之前,我們需要借助預處理器(SCSS/Less)來編寫嵌套語法。現在,CSS 終于原生支持嵌套語法了。下面就來看看原生 CSS 嵌套是怎么用的,未來是否還需要使用預處理器?
CSS 嵌套入門
使用 CSS 嵌套,可以編寫更少的代碼,并且代碼更易于閱讀和維護。沒有CSS嵌套時,我們只能這樣輸入完成的選擇器路徑:
.parent1 .child1,
.parent2 .child1 {
color: red;
}
.parent1 .child2,
.parent2 .child2 {
color: green;
}
.parent1 .child2:hover,
.parent2 .child2:hover {
color: blue;
}
使用新的嵌套語法,可以將子選擇器嵌套在父選擇器中:
.parent1, .parent2 {
.child1 {
color: red;
}
.child2 {
color: green;
&:hover {
color: blue;
}
}
}
我們可以將選擇器嵌套任意深度,但是最好不要超過三層,雖然沒有技術上的硬性限制,但是嵌套深度越大,會使代碼更難讀,并且生成的 CSS 可能會變得不必要的冗長。
CSS 嵌套語法
可以將任何選擇器嵌套在另一個選擇器中,但它必須以符號開頭,例如 &、.(用于HTML類)、#(用于HTML id)、@(用于媒體查詢)、:、::、*、+、~、> 或 [。換句話說,它不能直接引用HTML元素。下面這段代碼是無效的。
選擇器不會被解析。
.parent1 {
p {
color: blue;
}
}
需要使用 &
符號來引用當前選擇器:
.parent1 {
& p {
color: blue;
}
}
另外,還可以使用以下任一方法:
- > p:只會為 .parent1 的直接子元素設置樣式。
- :is(p):使用最具體選擇器的特異性。
- :where(p):特異性為零。
在這個例子中,它們都可以正常工作,但在更復雜的樣式表中,可能會遇到特異性問題。
& 還允許在父選擇器上針對偽元素和偽類進行定位,例如:
p.my-element {
&::after {}
&:hover {}
&:target {}
}
如果不使用&符號,將會選擇該選擇器的所有子元素,而不是p.my-element。(在Sass中也是如此。)
可以在選擇器的任何位置使用&,例如:
.child1 {
.parent3 & {
color: red;
}
}
這將轉換為以下非嵌套語法:
.parent3 .child1 { color: red; }
我們甚至可以在選擇器中使用多個&符號:
ul {
& li & {
color: blue;
}
}
這將選擇嵌套的<ul>元素(ul li ul),但不建議這樣做,因為這樣代碼會變得混亂。
除此之外,還可以嵌套媒體查詢。以下代碼將為段落元素應用青色,當瀏覽器窗口寬度至少為800像素時,將會覆蓋該樣式,并將段落元素的顏色更改為紫色。
p {
color: cyan;
@media (min-width: 800px) {
color: purple;
}
}
CSS 嵌套陷阱
原生嵌套將父選擇器封裝在:is()中,這可能會導致與Sass輸出的差異。
考慮下面的代碼:
.parent1, #parent2 {
.child1 {}
}
當在瀏覽器中解析時,這會變成了以下內容:
:is(.parent1, #parent2) .child1 {}
由于原生嵌套使用了:is()并根據其最具體選擇器(#parent2)的優先級來確定優先級,因此在.parent1內部的.child1元素具有101的優先級。而對于Sass編譯,會將相同的代碼編譯成不同的結構。
Sass 會將相同的代碼編譯為以下內容:
.parent1 .child1,
#parent2 .child1 {
}
在這種情況下,.parent1內部的.child1元素的優先級為002,因為它匹配了兩個類(#parent2被忽略)。它的選擇器比原生選項更不具體,因此在級聯中被覆蓋的可能性較大。
還可能遇到一個更微妙的問題,考慮以下代碼:
.parent .child {
.grandparent & {}
}
原生CSS的等效代碼如下:
.grandparent :is(.parent .child) {}
這將匹配以下順序錯誤的HTML元素:
<div class="parent">
<div class="grandparent">
<div class="child">MATCH</div>
</div>
</div>
MATCH 被應用樣式是因為CSS解析器執行了以下操作:
- 它找到所有具有class為child的元素,并且這些元素在DOM層次結構中的任意位置都有一個祖先元素為parent。
- 找到包含MATCH的元素后,解析器檢查是否存在一個祖先元素為grandparent,同樣是在DOM層次結構的任意位置。它找到了一個并相應地給該元素應用樣式。
但在Sass中,情況并非如此,它編譯成以下內容:
.grandparent .parent .child {}
由于HTML元素的類沒有遵循嚴格的grandparent、parent和child的順序,因此上面的HTML不會被應用樣式。
最后,Sass 使用字符串替換,因此以下聲明也是有效的,并且匹配任何具有outer-space類的元素:
.outer {
&-space { color: black; }
}
原生 CSS 會忽略&-space選擇器。
還需要預處理器嗎?
現在很多前端項目都還在借助CSS預處理器來編寫 CSS,以添加一下實用的功能到 CSS 中,例如 mixin、函數、嵌套等功能。在應用的構建過程中,會將這些代碼轉換為常規的 CSS,以便瀏覽器可以理解它。
Sass 開發團隊也宣布他們將支持在.css文件中使用原生CSS嵌套,并保持代碼不變。他們將繼續像以前一樣編譯嵌套的SCSS代碼,以避免破壞現有的代碼庫,但在全局瀏覽器支持達到98%時,將開始使用:is()選擇器。
現在,越來越多預處理器帶來的功能都在原生 CSS 中內置了,嵌套語法就是其中之一。雖然,嵌套語法已被主流瀏覽器支持,但是很多用戶還在使用舊版本的瀏覽器,無法很好的得到新語法的支持,并且個人認為,預處理器的嵌套語法相比原生嵌套語法更簡單易懂。所以,在很長一段時間內,我們仍然是需要借助預處理器來編寫 CSS。
除此之外,我們仍然需要借助 PostCSS 等后處理器來處理新的 CSS 功能,以將其轉化為瀏覽器可以理解的內容。