作者定義的 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 屬性「不會」由陰影 DOM 封裝。相反地,它們是用於在不同陰影樹之間傳遞參數的常用方式。這會讓 @property 描述元件變得特別:它應會像是文件全域型別宣告一樣運作,定義特定命名屬性的運作方式。由於屬性必須在陰影樹中比對,屬性宣告不相符會導致意外結果,因此 @property 宣告會依據文件順序指定為扁平化並解析。

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

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

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

  1. 首先,請檢查陰影內容中的樣式。這是零件的「預設」樣式。
  2. 然後套用 ::part 中定義的外部樣式。這是零件的「自訂」樣式。
  3. 接著,套用與 !important 一併定義的任何內部樣式。這可讓自訂元素宣告特定部分的特定屬性無法由 ::part 自訂。

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

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