作成者が定義した CSS 名と Shadow DOM: 仕様上と実際

作成者が定義した CSS 名と Shadow DOM は連携して動作します。ただし、ブラウザは仕様と一致しておらず、場合によっては互いに一致していません。また、CSS 名はすべて、わずかに異なる方法で不整合があります。

この記事では、作成者定義の CSS 名がシャドウ スコープ間でどのように動作するかの現状を説明します。これは、近い将来の相互運用性の向上に役立つことを願っています。

作成者定義の CSS 名とは

作成者定義の CSS 名は、比較的古い CSS 構文メカニズムです。これは、<keyframe-name> をカスタム識別子または文字列として定義する @keyframes ルール用に最初に導入されました。このコンセプトの目的は、スタイルシートの一部で何かを宣言し、別の部分で参照することです。

/* "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 で定義された名前とは異なります。CSS 名は、スタイルシートのコンテキスト内で宣言されて参照されます。

名前と Shadow DOM の関係

CSS 名はドキュメントまたはスタイルシートのさまざまな部分の間に関係を作成するように構築されていますが、Shadow DOM は反対のことをするように構築されています。関係をカプセル化するため、独自の名前空間を持つはずのウェブ コンポーネント間で漏洩しません。

CSS 名とシャドー DOM を組み合わせることで、柔軟性がありながら安定性も確保された、表現力豊かなウェブ コンポーネントの作成が可能になります。

理論上はこれは良い方法です。実際には、同じブラウザ内の機能間、ブラウザ間、機能と仕様間で、CSS 名が Shadow DOM と相互運用する方法に一貫性がありません。

名前と Shadow DOM の連携の仕組み

この問題を理解するには、理論上、CSS のこれらの部分がどのように連携する必要があるかを理解することが重要です。

一般的なルール

シャドー ツリー全体で CSS 名がどのように動作するかに関する一般的なルールは、CSS スコープ レベル 1 仕様で定義されています。要約すると、CSS 名は定義されているスコープ内でグローバルです。つまり、子孫のシャドー ツリーからはアクセスできますが、兄弟や祖先のシャドー ツリーからはアクセスできません。これは、同じツリー スコープ内にカプセル化される要素 ID などのウェブ プラットフォームの名前とは異なります。

ルールの例外: @property

他の CSS 名とは異なり、CSS プロパティは Shadow DOM でカプセル化されません。むしろ、さまざまなシャドウ ツリー間でパラメータを渡す一般的な手段です。これにより、@property 記述子は特別なものになります。これは、特定の名前付きプロパティの動作を定義するドキュメント全体の型宣言のように動作します。プロパティはシャドー ツリー間で一致している必要があるため、プロパティ宣言が一致しないと予期しない結果が生じます。そのため、@property 宣言は、ドキュメント順にフラット化され、解決されるように指定されています。

::part でルールがどのように機能するか

シャドウ部分は、シャドウ ツリー内の要素を親ツリーに公開します。これにより、親ツリーはその要素にアクセスし、::part 要素を使用してスタイルを設定できます。

::part では、2 つのツリー スコープが同じ要素にスタイルを適用できるため、次のカスケード順序が指定されます。

  1. まず、シャドウ コンテキスト内のスタイルを確認します。これは、パーツの「デフォルト」スタイルです。
  2. 次に、::part で定義されている外部スタイルを適用します。これは、パーツの「カスタマイズされた」スタイルです。
  3. 次に、!important とともに定義された内部スタイルを適用します。これにより、カスタム要素は、特定のパートの特定のプロパティが ::part でカスタマイズできないことを宣言できます。

つまり、::part はシャドウ スコープのスタイルではなくホスト スコープのスタイルであるため、シャドウ DOM 内の名前を ::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 属性を持つインライン スタイル、またはスクリプトを使用してプログラムでスタイルを設定するスタイルは、要素のスコープに制限されます。これは、要素にスタイルを適用するには、要素ハンドルにアクセスし、Shadow ルート自体にアクセスする必要があるためです。

CSS 名と Shadow DOM が実際に連携する仕組み

上記のルールは明確かつ一貫していますが、現在の実装では必ずしも反映されていません。実際には、@property はブラウザ間で一貫して仕様と異なる動作をします。また、他の機能のほとんどには未解決のバグがあります(一部はまだリリースされていないため、修正する時間があります)。

これらの機能が実際にどのように機能するかをテストして示すために、https://css-names-in-the-shadow.glitch.me/ というページを作成しました。このページには、いくつかの iframe があり、それぞれが 1 つの機能に焦点を当て、次の 6 つのシナリオをテストします。

  • 外側の名前への外部参照: Shadow DOM は使用されません。問題なく動作します。
  • 内部名への外部参照: シャドー コンテキストで定義された名前が漏洩することを意味するため、これは機能しません。
  • 外側の名前への内部参照: ツリー スコープの名前はシャドウルートに継承されるため、これは機能します。
  • 内部名への内部参照: 参照の名前が同じスコープにあるため、これは機能するはずです。
  • ::part が外部名を参照している: ::part と名前の両方が同じスコープで宣言されているため、これは機能します。
  • ::part 内部名への参照: 外部スコープは Shadow DOM 内で宣言された名前を認識できないため、これは機能しません。

@keyframes

仕様で定義されているように、@keyframes アットルールが祖先スコープ内にある限り、シャドウルート内からキーフレーム名を参照できます。実際には、この動作を実装しているブラウザはなく、キーフレーム定義は定義されているスコープでのみ参照できます。問題 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 に関する現在の相互運用状態のスナップショットを調べると、エクスペリエンスが不整合でバグがあります。ここで検討した機能のいずれも、ブラウザ間で一貫した動作をせず、仕様に準拠していません。ただし、エクスペリエンスを一貫させるための差分は、バグと仕様の問題の有限なリストです。これを修正しましょう。 なお、この記事で説明する不整合に困難を感じている場合は、この概要が役立つことを願っております。