Smashing Magazine 在 11 月 3 日刊出 Gabriel Shoyombo 的〈Modern CSS Might Make Your Front-End More Complex〉,主張新的原生 CSS 功能能讓單一檔案變短,卻讓整個應用更難推理。這對維護 design system 的人是很實際的提醒:元件庫常把舊的 JavaScript 版面邏輯換成容器查詢與 :has(),換完之後介面出現「沒人寫過的狀態」。讀到的程度:我用網頁擷取讀了這篇的全文(含基準測試表與參考資料);文中的效能數字出自作者自己的單頁、單一瀏覽器、單一機器,我無法重現,也沒有另外查證他提交的 Chromium 議題。今天不重複前兩天的主題,轉到元件實作這一層。

📖 學

一、局部簡單,不等於系統簡單

文章把複雜度拆成兩個量:局部簡單是一個檔案需要多少程式碼,系統複雜是修改前得先弄懂多少交互作用。diff 只看得到前者。原生功能只有在「完全取代舊實作、而且不干擾鄰居」時才真正降低複雜度。作者全文用「公寓與室友」當比喻:新功能搬進來,不代表舊住戶搬走了。

二、一個行為兩個擁有者

第一個情境最貼近設計系統。原本用 ResizeObserver 在卡片寬時加上 .card--wide,樣式靠 .card--wide .card__title 這類選擇器;改寫時在卡片上設 container-type: inline-size,再用 @container (width > 480px) 寫寬版樣式。問題是觀察器與舊選擇器常常沒刪。之後把斷點改成 600px,只更新了容器查詢這一邊,於是 480 到 600 像素之間出現一個沒人設計過的版面。文章的處理原則是:CSS 擁有由尺寸或 DOM 結構決定的呈現,JavaScript 擁有應用狀態與非同步工作,遷移完成時要把舊的類別、觀察器與選擇器一併刪掉。同類陷阱還有 JavaScript 切換 .is-active 又同時用 :has()、JS 與 CSS 轉場回應同一次互動、舊的視窗媒體查詢留在容器查詢旁邊。

三、容器宣告的副作用

container-type 除了建立樣式與行內尺寸的包含,還會建立 block formatting context。作者示範三個後果:內部浮動被包住、外部浮動被排除、外距不再折疊。實測中,標題 24px 的外距與段落 16px 的外距不再折疊,加成 40px,卡片從 30px 長到 54px;卡片旁邊有浮動圖片時,卡片被推進 120px 並縮窄 120px;在 inline-size 包含下,內容決定寬度的卡片(flex 項目、inline-block、浮動、fit-content、auto 格線軌道等)會縮成 0px,文中的標籤寬度 74、96、114px 全數歸零。對元件作者來說,意思是「在元件根節點加一行容器宣告」並不是局部修改,要在每個真實的宿主版面裡測過。

四、層疊與選擇器權重

:has() 本身不加權重,但括號內的選擇器會。文中例子:.card:has(img) p 權重 0-1-2,壓過 .comment p 的 0-1-1;留言頭像的 img 被 :has(img) 命中,結果沒有主圖的卡片裡不相干的段落失去外距。修法是收緊成 .card:has(> img) > p。原生巢狀也有類似問題:& 取選擇器清單中最高的權重,所以 .card, #featured { & a {...} } 的連結色權重是 1-0-1,被遠處的 #featured 拉高;用 :where() 包起清單可降到 0-0-1。cascade layers 也不是萬靈丹:未分層的規則一律贏過分層規則,一條分層的 0-4-0 選擇器輸給未分層的 .title;只有衝突雙方都放進層裡,層才有用。

五、效能:工作只是換人做

把工作從 JS 移到 CSS,改變的是誰做,不是做多少。作者在 Chrome 153 上用 5,000 列清單切換 .is-selected 300 次:JS 只改工具列的類別,每次低於 0.1ms;body:has(.row.is-selected) .toolbar 約 1.4ms;.list:has(.cell > b) .row 搭配 JS 低於 0.1ms;兩條 :has() 規則同時存在約 7ms,需要重算 5,001 個元素的樣式,與 JS 直接重算全部列的約 6ms 相近。清單加到 20,000 列時約 31ms。作者強調兩條規則單獨看都沒問題,成本是組合出來的。60Hz 一幀 16.7ms,7ms 還塞得進去,但為了改一個元素而重算每一列,仍是浪費。這組數字出自單頁、單瀏覽器,只能當警訊。

六、我的判讀

文章沒有談無障礙,影響只以視覺回歸呈現:卡片消失、平板上標題大小異常、留言文字被擠壓、大型清單的選取變慢。我的歸納(不是原文)是:這些全都是「元件在真實宿主裡跑出設計稿沒畫的狀態」,所以 design system 的元件驗收,除了 Figma 與 Storybook 的單件展示,還要有「塞進最醜版面」的測試。邊界也要說:作者是前端工程師的個人投稿,社群留言(r/webdev 等)只被引述為討論脈絡,不是證據。

🧠 記

  • 來源: Smashing,Gabriel Shoyombo,2026-11-03;基準測試為作者單頁單機(Chrome 153,2026 年 10 月)。
  • 局部簡單對系統複雜: diff 只看得到前者。
  • 一個行為一個擁有者: CSS 管尺寸與結構驅動的呈現,JS 管應用狀態與非同步;遷移後刪淨舊程式。
  • container-type 會建立 block formatting context: 浮動、外距折疊都可能改變,inline-size 包含下內容寬度元件可能歸零。
  • :has() 權重取括號內最高者: 用 > 收緊;巢狀清單用 :where() 降權重。
  • 未分層壓過分層: 層只在衝突雙方都分層時有用。
  • 效能: 兩條各自無害的 :has() 規則合併後 5,001 個元素被重算(作者測量,未重現)。

✍️ 實踐

用 40 分鐘審一個你負責的元件遷移。

  1. 挑一個剛從 JS 改成容器查詢或 :has() 的元件,搜尋舊的類別名稱與觀察器,確認有沒有殘留;殘留就刪,或寫下為何保留。
  2. 把元件放進四種宿主:旁邊有浮動圖片、在 flex 容器、在 inline-block、在 auto 欄寬的格線,看寬度與外距有沒有跑掉。
  3. 掃一遍所有 :has( 選擇器,把沒有 > 或 + 限縮的標出來,逐一確認命中範圍。
  4. 若有大型清單,用 DevTools 的 Performance 面板在實際資料量下切換選取狀態,看重算樣式的元素數。
  5. 把文章的六項檢查表(擁有者、範圍、層疊、遷移、效能、可維護性)貼進你的 PR 範本。

🔗 延伸學習

💬 問 AI

我要審查一個 design system 元件從 JavaScript 版面邏輯遷移到 CSS 容器查詢與 :has() 的過程。請分成「來源說的」「你查證到的」「你的推論」三區回答,不確定就直說,不要編造連結、數字或引文。
 
背景(請當待查證主張):
Smashing Magazine 2026-11-03〈Modern CSS Might Make Your Front-End More Complex〉稱:container-type 會建立 block formatting context 而改變外距折疊與浮動行為;:has() 的權重取括號內選擇器;兩條各自無害的 :has() 規則組合可能使 5,001 個元素被重算(作者單頁單機測量)。
 
請做:
A. 對照 MDN 與 CSS 規格,逐項說明這三個主張成立、部分成立或查不到,附連結。
B. 給我一份遷移檢查表,涵蓋行為擁有者、影響範圍、層疊位置、舊程式刪除、效能量測、可維護性。
C. 設計 4 個「最醜宿主版面」測試案例,說明各自要觀察什麼。
D. 說明如何用 DevTools 驗證 :has() 造成的重算範圍。
E. 列出反方觀點:哪些情況下保留 JS 反而更好。
只談介面實作與可用性,不評論公司或人事。