CSS大解剖 6日目: 「スタイルシート 1/2」
本稿は、2024年2月頃に書き溜めていたシリーズです。最後まで温存させるのが勿体ないので、未完成ですがそのまま公開します(公開日: 2025/9/22)。そのため、内容の重複や記述方針の不一致があるかもしれませんが、ご理解ください。
CSSの仕様を理解するために、1日ごとにテーマを決めて説明する企画6日目です。今日のテーマは「スタイルシート」です。
文書にスタイルを適用する方法
文書にスタイルを適用する方法はWorking DraftであるCSSOMに規定されており、大きく2種類に分けられます。
- 文書にスタイルシートをリンクする。
- 要素に直接スタイルを設定する。
文書にスタイルシートをリンクするには以下の方法があります。
-
Link:
HTTPヘッダーを送信する。これはCSSOMに規定はあるものの、実際には実装が進んでいないようです。 <link rel="stylesheet">
要素を追加する<style>
要素を追加する<style>
SVG要素を追加する-
<?xml-stylesheet?>
処理命令を追加する ※主にXHTMLを想定 - 文書の
adoptedStyleSheets
プロパティにCSSStyleSheetオブジェクトを追加する - 他のスタイルシートから
@import
at-rule によって別のスタイルシートを参照する。
スタイルシートとstyle属性を操作する
スタイルシートや要素のstyle属性はDOMからも操作できますが、構文解析済みのオブジェクトを直接操作することもできます。これを定義しているのがWorking DraftのCSSOMです。
スタイルシートを操作する
スタイルシートを操作するには、 LinkStyle.styleSheetやDocumentOrShadowRoot.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 と組み合わせて前方互換性を確保するために使われるほか、詳細度をどうしても差別化できない場合や、単に意図せずに詳細度が同じになってしまった場合にも使われることがあるので、意識しておくのがいいでしょう。
スタイルシート内の出現順は、ソースコード内の記載順で決定できます。異なるスタイルシートの間の関係は以下の順番です。
-
Link:
HTTPヘッダー。ヘッダーとしての出現順でソートされる。 -
<link rel="stylesheet">
要素、<style>
要素、<style>
SVG要素、および<?xml-stylesheet?>
処理指令。スタイルシートを参照する要素の、DOM上の木順序でソートされる。 - 文書の
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
が以下のように解釈されたことを示唆しています。
-
index.html
がロードされる-
index.html
からa.css
がロードされる-
a.css
からb.css
がロードされる-
b.css
からa.css
が呼び出されるが、循環参照となるため無視される。 -
::before
に"b.css"
がセットされる
-
-
::before
に"a.css"
がセットされる
-
-
index.html
からc.css
がロードされる-
::before
に"c.css"
がセットされる
-
-
index.html
からb.css
がロードされる (2回目)-
b.css
からa.css
がロードされる (2回目)-
a.css
からb.css
が呼び出されるが、循環参照となるため無視される。 -
::before
に"a.css"
がセットされる
-
-
::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日目: 「仕様書」
- 2日目: 「文書とボックス木」
- 3日目: 「閲覧環境 1/3」
- 4日目: 「閲覧環境 2/3」
- 5日目: 「閲覧環境 3/3」
- 6日目: 「スタイルシート 1/2」
- 7日目: 「スタイルシート 2/2」
- 8日目: 「構文 1/2」
- 9日目: 「構文 2/2」
- 10日目: 「レイアウトループ」
- 11日目: 「セレクター 1/3」
- 12日目: 「セレクター 2/3」
- 13日目: 「セレクター 3/3」
- 14日目: 「カスケード」
- 15日目: 「方向」
- 16日目: 「ボックス」
- 17日目: 「文脈」
- 18日目: 「display」
Discussion