作者定義的 CSS 名稱和 shadow DOM:詳細規格和實際應用

作者定義的 CSS 名稱和陰影 DOM 應可搭配運作。不過,瀏覽器與規格不一致,有時彼此一致,且每個 CSS 名稱的方式也略有不同。

本文將說明作者定義的 CSS 名稱在陰影範圍中運作的方式,希望能做為指南,在近期內改善互通性。

什麼是作者定義的 CSS 名稱?

作者定義的 CSS 名稱是相對舊的 CSS 語法機制,最初為 @keyframes 規則導入,會將 <keyframe-name> 定義為自訂 ID 或字串。這個概念的目的是在樣式表中宣告某些內容,然後在其他部分參照。

/* "fade-in" is a CSS name, representing a set of keyframes */
@keyframes fade-in {
  from { opacity: 0 };
  to { opacity: 1 }
}

.card {
  /* "fade-in" is a reference to the above keyframes */
  animation-name: fade-in;
}

其他使用 CSS 名稱的 CSS 功能包括字型、屬性宣告、容器查詢,以及較新的檢視畫面轉場、錨點定位和捲動驅動動畫。下表列出 Chrome 會檢查狀態的名稱 (不含所有名稱)。

功能 名稱宣告 名稱參照
主要畫面格 @keyframes animation-name
字型 @font-face { }
@font-palette-values
font-family
font-palette
屬性宣告 @property
任何未註冊的自訂屬性宣告
var()
查看轉場效果 view-transition-name
view-transition-class
::view-transition-* 個虛擬元素
錨定定位 anchor-name position-anchor
捲動觸發的動畫 view-timeline-name
scroll-timeline-name
animation-timeline
清單樣式 @counter-style list-style
計數器 counter-reset
counter-set
counter-increment
容器查詢 container-name @container
頁面 page @page

如表格所示,CSS 名稱通常會對應到 CSS 參照。例如,animation-name@keyframes 名稱的參照。CSS 名稱與 DOM 中定義的名稱 (例如屬性和標記名稱) 不同,因為它們會在樣式表格中宣告及參照。

名稱與 Shadow DOM 的關聯

雖然 CSS 名稱的設計目的是在文件或樣式表單的不同部分之間建立關係,但 Shadow DOM 的設計目的則是相反。它會封裝關係,以免在應有自己命名空間的網頁元件之間發生資料外洩。

將 CSS 名稱和陰影 DOM 結合後,您應該會發現,組合網頁元件的體驗既靈活又穩定,既能充分表達,又能維持穩定性。

理論上來說,這麼做是正確的。實際上,瀏覽器在 CSS 名稱與 shadow DOM 互動的方式上並不一致,無論是同一個瀏覽器中的功能、不同瀏覽器之間,或是功能與規格之間都是如此。

名稱和陰影 DOM 的搭配運作方式

為了瞭解問題,建議您瞭解 CSS 的這些部分在理論上應如何搭配運作。

通則

CSS Scoping Level 1 規格定義了 CSS 名稱在陰影樹狀結構中的行為一般規則。總結來說,CSS 名稱在其定義的範圍內是全域的,也就是說,可以從子系別影像樹存取,但無法從同層或祖系影像樹存取。請注意,這與網路平台的名稱不同,例如在同一個樹狀結構範圍內封裝的元素 ID。

例外狀況:@property

與其他 CSS 名稱不同,CSS 屬性「不」以 shadow DOM 封裝。 相反地,它們是用於在不同陰影樹之間傳遞參數的常用方式。這會使 @property 描述元具有特殊性:其行為應像 Document-global 類型宣告一樣,以定義特定具名屬性的行為方式。由於屬性必須跨陰影樹狀結構相符,屬性宣告不相符會產生非預期的結果,因此必須指定 @property 宣告,讓系統根據文件順序加以整併並解決。

規則應如何與 ::part 搭配運作

陰影元件會將陰影樹狀結構中的元素公開給其父項樹狀結構。這樣一來,父項樹狀結構就能存取該元素,並使用 ::part 元素為其設定樣式。

由於 ::part 允許兩個樹狀結構範圍設定同一個元素的樣式,因此指定了以下串聯順序:

  1. 首先,請檢查陰影內容中的樣式。這是零件的「預設」樣式。
  2. 然後套用 ::part 中定義的外部樣式。這就是該部分的「自訂」樣式。
  3. 然後套用搭配使用 !important 定義的所有內部樣式。這可讓自訂元素宣告特定部分的特定屬性無法由 ::part 自訂。

這表示在陰影 DOM 中的名稱無法從 ::part 參照,因為 ::part 是主機範圍樣式,而不是陰影範圍樣式。例如:

// inside the shadow DOM:
@keyframes fade-in {
  from { opacity: 0}
}

// This shouldn't work!
// The host style shouldn't know the name "fade-in"
::part(slider) {
  animation-name: fade-in;  
}

規則應與內嵌樣式搭配運作的方式

::part 不同,內嵌樣式 (含有 style 屬性) 或以程式輔助方式使用指令碼設定樣式的內嵌樣式,會以元素的範圍為範圍。這是因為您需要存取元素句柄,才能將樣式套用至元素,進而套用至陰影根。

CSS 名稱和陰影 DOM 在實際情況下如何搭配運作

雖然上述規則已明確定義且一致,但目前的實作方式不一定反映這項規則。實際運作時,@property 與規格在不同瀏覽器中的運作方式會有所不同,且大多數其他功能都有未解決的錯誤 (有些功能尚未發布,因此有時間修正)。

為了測試並展示這些功能在實際情況下如何運作,我們建立了以下網頁:https://css-names-in-the-shadow.glitch.me/。本頁面有多個 iframe,每個 iframe 分別用於其中一項功能,以及測試六個情境:

  • 外部參照外部名稱:不涉及陰影 DOM,應該可正常運作。
  • 外部參照內部名稱:這不應運作,因為這表示在陰影內容中定義的名稱已外洩。
  • 內部參照外部名稱:這應該可行,因為樹狀範圍名稱會由陰影根繼承。
  • 內部參照至內部名稱:這應該會運作,因為參照的名稱都位於相同範圍內。
  • ::part 參照外部名稱:這應正常運作,因為 ::part 和名稱都是在相同範圍內宣告。
  • ::part 參照內部名稱:這不應該運作,因為外部範圍不應取得陰影 DOM 內宣告的名稱。

@keyframes

根據規格定義,只要 @keyframes at-rule 位於祖系範圍內,您就能從陰影根目錄中參照主要畫面格名稱。實際上,瀏覽器不會實作這個行為,因此主要畫面格定義只能在定義的範圍中參照。請參閱問題 10540

@property

如規格說明所定義,任何 @property 宣告都會扁平化至文件範圍。不過,目前在所有瀏覽器中,您只能在文件範圍內宣告 @property,系統會忽略陰影根目錄中的 @property 宣告。
請參閱問題 10541

瀏覽器專屬錯誤

其他功能在不同瀏覽器中顯示的行為不一致:

  • @font-face 會在 Safari 中扁平化為根範圍。
  • Chromium 不允許在陰影根目錄中繼承 anchor-name 規則
  • scroll-timeline-nameview-timeline-name::part (也包括在 Chromium 中) 上沒有正確的範圍。
  • 沒有任何瀏覽器允許在陰影根目錄中宣告 @font-palette-values
  • view-transition-class 可在陰影根目錄中定義 (轉場本身位於陰影根目錄之外)。
  • Firefox 可讓 ::part 存取內部陰影名稱 (容器查詢、關鍵影格)。
  • Firefox 和 Safari 不會遵循陰影根中的 @counter-style

請注意,counter-resetcounter-setcounter-increment 的規則略有不同,因為它們是隱含名稱,而宣告 CSS 屬性則有已建立且經過充分測試的一組規則。

結論

壞消息是,當我們檢查 CSS 名稱和 shadow DOM 的目前互通狀態快照時,發現體驗不一致且有錯誤。我們在這裡檢查的功能全都在瀏覽器之間的行為不一致,也不符合規格。好消息是,要讓體驗一致,差異只會是有限的錯誤和規格問題清單。讓我們一起解決這個問題! 在此同時,如果您遇到本文所述的資料不一致問題,這份總覽或許能提供協助。