作成者が定義した 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 内で定義される名前(属性やタグ名など)とは異なります。これは、スタイルシートのコンテキスト内で宣言され、参照されるためです。

名前と 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 は Shadow スコープのスタイルではなくホストスコープのスタイルであるため、Shadow 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 ルールを継承できない
  • ::partscroll-timeline-nameview-timeline-name のスコープが正しく設定されていません(Chromium でも同じです)。
  • シャドウルートで @font-palette-values を宣言することは、どのブラウザでも許可されていません。
  • view-transition-class はシャドールート内で定義できます(遷移自体はシャドールートの外部にあります)。
  • Firefox では、::part が内側のシャドウ名(コンテナクエリ、キーフレーム)にアクセスできます。
  • Firefox と Safari は、シャドールートの @counter-style を尊重しません。

counter-resetcounter-setcounter-increment は暗黙的な名前であるため、ルールが若干異なります。CSS プロパティの宣言には、確立された、十分にテストされた一連のルールがあります。

まとめ

悪いニュースとして、CSS 名と Shadow DOM に関する現在の相互運用状態のスナップショットを調べると、エクスペリエンスに一貫性がなく、バグが伴います。ここで検討した機能は、ブラウザ間で一貫した動作をせず、仕様に準拠していません。ただし、エクスペリエンスを一貫させるための差分は、バグと仕様の問題の有限なリストです。これを修正しましょう。 なお、この記事で説明する不整合に困難を感じている場合は、この概要が役立つことを願っております。