💡

CSS大解剖 6日目: 「スタイルシート 1/2」

に公開

本稿は、2024年2月頃に書き溜めていたシリーズです。最後まで温存させるのが勿体ないので、未完成ですがそのまま公開します(公開日: 2025/9/22)。そのため、内容の重複や記述方針の不一致があるかもしれませんが、ご理解ください。


CSSの仕様を理解するために、1日ごとにテーマを決めて説明する企画6日目です。今日のテーマは「スタイルシート」です。

文書にスタイルを適用する方法

文書にスタイルを適用する方法はWorking DraftであるCSSOMに規定されており、大きく2種類に分けられます。

文書にスタイルシートをリンクするには以下の方法があります

スタイルシートとstyle属性を操作する

スタイルシートや要素のstyle属性はDOMからも操作できますが、構文解析済みのオブジェクトを直接操作することもできます。これを定義しているのがWorking DraftのCSSOMです。

スタイルシートを操作する

スタイルシートを操作するには、 LinkStyle.styleSheetDocumentOrShadowRoot.styleSheets等のインターフェースを通じてCSSStyleSheetオブジェクトを取得し、このオブジェクト上の所定のプロパティやメソッドを呼び出します。

スタイルシートに関して、DOMからCSSOMへの関係は一方向的です。つまり、DOMを更新するとCSSOMは更新されますが、CSSOMを更新してもDOMは更新されません。

  • <link> 要素の href を更新するとCSSStyleSheetが再作成されますが、CSSStyleSheetに変更を加えても <link> 要素の href は変わりません。
  • <style> 要素内のテキストを更新するとCSSStyleSheetが再作成されますが、CSSStyleSheetに変更を加えても <style> 要素内のテキストは変わりません。

style属性を操作する

style属性を操作するには、ElementCSSInlineMixin.styleによりCSSStyleDeclarationオブジェクトを取得し、このオブジェクト上の所定のプロパティやメソッドを呼び出します。

style属性に関して、DOMとCSSOMの関係は双方向的です。つまり、DOMで style 属性を更新 (setAttribute) すると style プロパティの返すCSSStyleDeclarationからも変更が観測されます。 style プロパティの返すCSSStyleDeclarationに変更を加えると、DOM上の style 属性 (getAttribute) にも変更が反映されます

style 属性の書き戻しを行うために、CSSOMの構文木からCSS構文への直列化アルゴリズムが定義されています。 style の書き戻しに際して、コメントや空白、値の別名などの詳細は失われます。

仕様には明記されていませんが、 style プロパティが返すCSSStyleDeclarationオブジェクトはメモ化されており、要素ごとに常に同じオブジェクトを返すようです。

出現順

カスケードの規則では、オリジンや詳細度など他の条件が同じルールは、その出現順によってカスケードすると定められています。この規定はエラー許容パーサーの挙動や @supports at-rule と組み合わせて前方互換性を確保するために使われるほか、詳細度をどうしても差別化できない場合や、単に意図せずに詳細度が同じになってしまった場合にも使われることがあるので、意識しておくのがいいでしょう。

スタイルシート内の出現順は、ソースコード内の記載順で決定できます。異なるスタイルシートの間の関係は以下の順番です。

  1. Link: HTTPヘッダー。ヘッダーとしての出現順でソートされる。
  2. <link rel="stylesheet"> 要素、 <style> 要素、 <style> SVG要素、および <?xml-stylesheet?> 処理指令。スタイルシートを参照する要素の、DOM上の木順序でソートされる。
  3. 文書の adoptedStyleSheets プロパティ。配列内の出現順がそのまま使われる。

加えて、 @import at-rule で参照されているスタイルシートは、 @import が書かれた位置に挿入されたのと同等とみなされます。@import は他のほとんどのat-ruleや宣言より前に書く必要があるため、これは実質的には @import を深さ優先探索したときの返りがけ順と同等ということになります。

循環参照

では、 @import が循環参照している場合はどうでしょうか。これは標準の規定にはないようですが、FirefoxとChromeで実験したところ、循環参照が起きる手前の @import までは正常に処理し、循環参照をトリガーする @import は無視するというような挙動であると推測されます。たとえば以下のような例を考えます。

<!-- index.html -->
<!doctype html>
<style>
  @import url(a.css);
  @import url(c.css);
  @import url(b.css);
</style>
<div id="test-target"></div>
/* a.css */
@import url(b.css);

#test-target::before {
  content: "a.css";
}
/* b.css */
@import url(a.css);

#test-target::before {
  content: "b.css";
}
/* c.css */
#test-target::before {
  content: "c.css";
}

この場合、 index.html を開くと b.css が表示されます。これは、 @import が以下のように解釈されたことを示唆しています。

  1. index.html がロードされる
    1. index.html から a.css がロードされる
      1. a.css から b.css がロードされる
        1. b.css から a.css が呼び出されるが、循環参照となるため無視される。
        2. ::before"b.css" がセットされる
      2. ::before"a.css" がセットされる
    2. index.html から c.css がロードされる
      1. ::before"c.css" がセットされる
    3. index.html から b.css がロードされる (2回目)
      1. b.css から a.css がロードされる (2回目)
        1. a.css から b.css が呼び出されるが、循環参照となるため無視される。
        2. ::before"a.css" がセットされる
      2. ::before"b.css" がセットされる

興味深い点として、循環参照となるような呼び出しは回避するものの、同じスタイルシートを2回読み込むこと自体は許容していることが挙げられます。

いずれにせよ、循環参照がある場合、ループ内での出現順がループの開始位置に依存して変化するなど状況を複雑にしてしまいます。なるべく避けるほうがよいでしょう。 (そもそも、スタイルシートの出現順に依存するのがあまり望ましくなさそうですが)

スタイルシートが適用される条件

文書中のスタイルシートが実際に有効なCSSスタイルシートとして適用されるには、以下の条件を満たしている必要があります。

  • 各要素に固有の条件を満たし、CSSStyleSheetオブジェクトが作成されていること。
  • スタイルシートが有効である (disabled がセットされていない) こと。
  • スタイルシートの media 指定が条件に合致すること。

スタイルシートのdisabledプロパティと代替スタイルシート

スタイルシートには有効/無効の概念があり[1]disabledプロパティを通じてJavaScriptから操作することができます。

加えて、これは代替スタイルシート機能の実現にも使われます。代替スタイルシートは、Webサイトが複数の異なるスタイルを定義しておき、ユーザーがそのうちの1つを選択できるようにするというものです。現代ではprefers-color-schemeなどの既定のメディアクエリで実現されることが多く、代替スタイルシートの実装を持たないWebブラウザも多くあります。

代替スタイルシート機能を実現するために使われるのがtitleプロパティalternateフラグです。これによりスタイルシートは3種類に分けられます。

  • titleを持たないスタイルシートは永続スタイルシート (persistent style sheet) などと呼ばれ、ユーザーの選択によらず適用されます。
  • titleを持つスタイルシート。これらは、ユーザーの選択に応じて有効化・無効化されます。
    • 初期状態で選択されるスタイルシートを優先スタイルシート (preferred style sheet) といいます。スタイルシートの追加時に代替フラグを外しておくと、優先スタイルシートになります。
    • 初期状態で選択されていないスタイルシートを代替スタイルシート (alternate style sheet) といいます。スタイルシートの追加時に代替フラグをつけると、代替スタイルシートになります。

ユーザーはWebブラウザのUIなどを通じて、希望するスタイルのtitleをひとつ選択します。すると、選択したtitleと同じtitleを持つスタイルシートは全て有効化され、異なるtitleを持つスタイルシートは全て無効化されます

title設定のデフォルト値は、最初に発見された優先スタイルシートのtitleとして決定されます。

media 指定

スタイルシートにはmedia属性を設定することが可能です[2]

media属性には @media at-ruleと同様のメディアクエリを書くことが可能で、基本的にはスタイルシート全体を @media で適切に囲ったものと同等と考えられます。しかし、スタイルシートの呼び出し側にmediaが設定されていることによって、必要のないリソースの読み込みをスキップすることが可能な場合があります。


脚注
  1. 実は、本稿執筆時点では、関連する仕様のどこにも「disabledプロパティが設定されたスタイルシートは適用除外される」とは書かれていないようです。しかし、常識に照らし合わせて考えれば、このような効果があることは間違いないでしょう。 ↩︎

  2. 実は、本稿執筆時点では、関連する仕様のどこにも「スタイルシートのmediaプロパティに設定されたクエリがfalseを返した場合、当該スタイルシートは適用除外される」とは書かれていないようです。しかし、常識に照らし合わせて考えれば、このような効果があることは間違いないでしょう。 ↩︎

Discussion