巢狀CSS (CSS nesting)終於從 Chrome 版本號 120 開始支援,去年(2023/8左右) Firefox 就支援 CSS nesting 了,以前寫 SCSS 的老人們可以放心直接寫,不用再編譯嗎? 並沒有,本文就來做一些實測。

巢狀結構寫法,有時候可以提升 CSS 樣式碼的易讀性和可維護性,如下圖所示,

左邊是 SCSS,右邊是編譯出來給瀏覽器看的 CSS。
人類也可以直接寫出右邊的 code,但是要多打了一大堆重複的部份,
反觀左邊的看起來輕便不少。
如果是用程式碼字數來算錢的,那左邊就是讓國家 GDP 和稅收大幅下降的罪人,
如果是依照檔案數量來算錢,那一個專案中多出一倍的檔案(原始檔和編譯後的檔案),還有一堆 npm 工具產生的檔案和資料夾,大家都將成為新的富翁!

巢狀 CSS 寫法很舒服,但是以前的網頁瀏覽器都看不懂,
開發者必須要用 SCSS/SASS 這種預處理器(preprocessors) 把 CSS 先編譯過,樣式寫得好不好先不論,整個編譯 CSS/JS 相關 toolchain 的演進又是一串辛酸史。
現在瀏覽器直接內建支援了!
距離工具自由可能又更近了一步。

雖然蘋果大魔王 iOS16.4 以下都不支援瀏覽器原生的 CSS nesting,開發者可能要兩三年後 iOS 20 公開發布,iPhone 出到 19 了,大家把 iOS 作業系統都升級上來了,才敢放心在專案中使用。
不過還是值得先試試,
至少看看瀏覽器原生的,能不能做出跟 SCSS Nesting 一模一樣的功能?
直接上範例樣式,這樣每次回來看,就知道支援度到哪邊…

把 media query 放進去

(可以拉動邊框右下角來模擬不同的視窗大小)

 .test1{
        padding:5px 10px;
        background:#ccc;
        margin:0 auto 10px;
        max-width:1000px;
        width:100%;
        @media (orientation:landscape){
            background:#000;
        }
        .landscape{color:#ccc}
        &:after{
            content:'';
            display: block;
            width: 100%;
            background: yellow;
            margin-top: 10px;
            @media (max-width:300px){
                background:brown;color:#fff;
            }
        }
 
    }

會發現偽元素裡的那段 media query 沒有如預期變成咖啡色,但是如果用 SCSS 編譯出來是正常的。
網路上查到說是 W3C 的規格 The nesting selector cannot represent pseudo-elements
所以會用偽元素的可能要小心…

多層多組 selector 與各種 combinators

刻意模擬多包幾層的方式,看看是否有不能被包在巢狀裡面的?

.test2{
  .alert, .warning {
    ul, p {
      background:red;color:#fff;margin:0;
    }
    ul{
      list-style:none;padding:0;margin:0;
      ~ p{background:#229241;color:#fff}
      + p{background:#0066cc;color:#fff}
      >li:nth-child(1){
        background:#000;
        &:before{content:'要顯示偽元素';display:inline;color:#ccc}
        &:after{content:'游標移入會變黃色背景';display:inline;color:#ccc}
        &:hover{background:yellow}
      }
    }
  }
}

在巢狀中用上了 nth-child, :hover, \~, +,至少包了兩三層,一切看起來都還正常。

has 選擇器、屬性選擇器和其他

 .test3{
  *{background:#0066cc;color:#fff;list-style:none;padding:0;margin:0}
  li:has(span) {background:#229241}
  li:has(input[type=checkbox]) {background:red}
  #test3{background:yellow;color:#000}
}

在巢狀中選到了 ID,另外還用了 has 選擇器來選 tag 或特定屬性的 input,也都能正常吃到樣式。

CSS 變數與 calc

.test4 {
     width: 200px;
     aspect-ratio: 1;
     --corner1:30%;
     --corner2:70%;
     clip-path: polygon(
      var(--corner1) 0%,
      var(--corner2) 0%,
      100% var(--corner1),
      100% var(--corner2),
      var(--corner2) 100%,
      var(--corner1) 100%,
      0% var(--corner2),
      0% var(--corner1)
     );
 
     background: linear-gradient(to right,#5ec7c1,#20e3b2,#0cebeb);
     display:flex;align-items:center;justify-content:center;
     span{background:#fff;aspect-ratio: 1;display:block;width:calc(var(--corner2) - var(--corner1))}
}

把 CSS 變數寫在巢狀中,還用 calc 來作減法運算,看起來也都正常。
ps.如果要畫八角型還可以用 css 的三角函數,用了 tan 之後就只需要一個變數,可參考 Temani Afif 的範例

巢狀簡寫

碰到 CSS 屬性或是 class name 開頭一樣的,在 SCSS/SASS 還有 Nested Properties 之類的特殊寫法可以用。
例如 background-color, background-repeat…系列的,或 font-weight, font-size… 系列的 CSS 屬性,
或是 UI 元件的內部零件,命名規則像是 .card-body, .card-footer 這種,
一養的東西只要打一次,用巢狀結構包好,編譯後會把省略的地方通通補好。

來測試瀏覽器原生的巢狀 CSS 是否支援這種寫法。

        .test5{
            font: {
                family: 'Noto Sans TC', sans-serif;
                size: 18px;
                weight: 600;
            }
            [class^="card-"] {padding: 5px 10px;color:#fff}
            .card {
                background: #f4f4f4;
                &-head {background: green;}
                &-body {background: #000;}
                &-footer {background: #0066cc;}
            }
        }

結果看起來目前是不支援,可惜。

其他

蒐集一些網路上看到的 CSS nesting 有趣用法。

看不到貼文此點此 T. Afif @ CSS Challenges
比較使用了 .main &{} 在 CSS 與 SCSS 的差異。


看不到貼文此點此 Lea Verou – Realization: CSS Nesting also allows you to basically do “else” clauses in selectors.

a, abbr, acronym, b, bdo, big, br, cite, code, dfn, em, i, kbd, label, mark,
output, samp, small, span, strong,  sub, sup, time, tt, var {
    outline: 1px dashed hsl(220 10% 50% / 50%) !important;
    :not(&) {
        outline: 1px dashed red !important;
    }
}

使用了 :not(&) 來達成類似 if-else 的目標,除了某些元素之外,通通套用另一組樣式。

心得

目前看起來瀏覽器原生的 CSS Nesting 還無法完全做到 SCSS/SASS 的 Nesting,就更別提 SCSS/SASS 現有的其他功能了。

這功能有點姍姍來遲,遲到甚至調樣式都不一定需要寫 CSS 了,
可能用 TailwindCSS 這種原子式 CSS
或是一些設計工具產出 code 再修,
或是用 No Code 工具在密密麻麻的屬性視窗中選擇樣式數值之類的,
但這些方式不一定適用所有工作流程,可能還有一堆現有用 SCSS 刻樣式的網站要維護。

除了使用者要將瀏覽器要更新到支援的版本,
才能正常顯示 CSS Nesting 寫的樣式之外,

對於開發人員來說,又有東西需要更新。
像是有些線上或本機的程式碼編輯器,
或是在網頁上幫程式碼加 Syntax highlighting 的套件。
不然碰到這種比較新的寫法,會顯示像是打錯字一樣的提示,或是變色變得怪怪的,
或是每打一行按 Enter 就亂補 } 括號,
會有點討厭喔。