🌊

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

に公開

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


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

<link> 要素は、文書と別のリソースとの関係をあらわします。その意味論は <a> などの他のリンク要素とあわせて、HTMLのLinksの章で詳細に定義されています。

<link> におけるリンク元文書とリンク先文書の関係を示すのが rel 属性です。本稿において重要なのは以下の2つの値です。

  • stylesheet …… リンク先文書はリンク元文書のスタイルシートである。
  • alternate …… 本来の意味は「リンク先文書はリンク元文書の代替表現である」ですが、 stylesheet と併用したときは意味が変わり、「このスタイルシートは代替スタイルシートである」という意味になります。

<link rel="stylesheet"> で指定されたリンクは意味的にはスタイルシートであるといえますが、これが実際にCSSStyleSheetオブジェクトを生成するには、以下の3つの条件を満たす必要があります。

  • この要素が文書に紐付いていること。
  • リンクが無効化されていないこと。
  • リンク先に text/css 形式のリソースが存在すること。

リンクの無効化

<link> 要素が disabled 属性を持つときは、リンクが無効化されています。この場合、リソースは取得されず、スタイルシートは生成されません

なお、スタイルシートの有効/無効リンクの有効/無効とは異なる概念なので注意しましょう。

リンク先リソースの検査

リンク先リソースのContent Typeが text/css でなければ、スタイルシートは生成されません

リソースのContent Typeの決定は複雑です。まず <link> 要素の type 属性がContent Typeのヒントとして使われます。もし type 属性に指定されたContent Typeがサポートされていなければ読み込みは拒否されます。いっぽう、リソースが読み込まれたあとは読み込んだリソースからContent Typeを決定するべきであり、Webブラウザがこの段階で type 属性を使うことは禁止されています

リソースを取得する場合、HTTPレスポンスヘッダにContent-Typeが含まれていることが期待されますが、現実的には正しくないContent-Typeが設定されているケースが多く存在します。このような場合の対処方法がMIME Sniffingで標準化されており、Webブラウザはこの規定に従うことになります。Content-Typeヘッダが無く推論も行われなかった場合はtext/css が仮定されます。また、Quirksモードでは常に text/css を仮定するように規定されています。

要素の配置

この種類の <link> 要素は <head> 内のみならず <body> 内に置くことも許されます

Link: HTTPヘッダ

Web LinkingはHTMLの <link> タグの意味論をHTML以外のWebリソースに対して一般化する形で定義したものです。加えて、このRFCではHTML以外の文書からリンクを行うために Link: ヘッダを規定しています。

Link: ヘッダが <link> を踏襲する形で定義されていることから、名目上はこのヘッダを使うことでもスタイルシートへのリンクを記述することができます。しかし、この機能は実際には広く実装されていないことが知られています。この不一致を反映する形で、標準間でも記述に差異が見られます。

<style> 要素

<style> 要素はHTML文書中にスタイルシートのソースコードを直接埋め込むための要素です。

<style> 要素が実際にCSSStyleSheetオブジェクトを生成するには、以下の2つの条件を満たす必要があります。

  • この要素が文書に紐付いていること。
  • リソースの形式が text/css であること。

disabledフラグ

<style> 要素はDOM上でdisabledプロパティを持ちますが、このプロパティは <style> 要素に対応するCSSStyleSheetオブジェクトのdisabledプロパティに対するエイリアスでしかありません。 <link> とは挙動が異なるため注意しましょう。

リソース形式の検証

type 属性に text/css 以外を設定している場合は、この要素はスタイルシートを生成しません

この挙動は、たとえばJavaScriptを使ってSassのような他のスタイルシート言語をサポートする場合に利用することができます。

要素の配置

この要素は <head> 内にのみ置くことが許されます。しかし、実際には <body> 内に <style> 要素を配置するような例もあるようです。筆者が知る範囲だと、一部のCSS-in-JSライブラリはSSR時にこのような挙動になります。

<style> SVG要素

<style> SVG要素はHTMLのそれとほぼ同じです。

<?xml-stylesheet?> 処理命令

<?xml-stylesheet?> 処理命令はXMLにおけるスタイルシート指定の手段のひとつです。XSLの指定によく用いられますが、CSSを紐付けることも可能です。その挙動はCSSOMに規定されています

HTML構文はXML処理命令を不正なコメントとして処理するため、HTMLファイルで静的にこの処理命令を記述することはできません (XHTML構文であれば記述可能です)。しかしHTMLでも、以下のようにJavaScriptから挿入することは可能です。

// <?xml-stylesheet href="style.css" type="text/css"?>
const pi = document.createProcessingInstruction("xml-stylesheet", 'href="style.css" type="text/css"');
document.insertBefore(pi, document.documentElement);

実験したところ、この方法でChrome, Firefoxともにスタイルシートを適用できるようです。

HTMLでわざわざこの形の指定をする意味はほぼないと思いますが、SVGであれば検討の余地があるかもしれません。

adoptedStyleSheets

CSSStyleSheetオブジェクトは、その由来から大きく2種類に分けられます。

  • non-constructed style sheet ... <link rel="stylesheet"> などの宣言に紐付けられる形で自動的に生成されるCSSStyleSheetオブジェクト。
  • constructed style sheet ... JavaScriptから手動で作成したCSSStyleSheetオブジェクト。

constructed style sheetを文書に適用するには、文書のadoptedStyleSheetsプロパティを利用します。これはCSSStyleSheetの配列(風のビューオブジェクト)です。

同じconstructed style sheetを複数の文書に適用することも可能です。

CSS Module Script

CSS Module Scriptは、ブラウザ上でJavaScriptから外部CSSファイルをパースさせる仕組みです[1]。これはECMAScriptのImport Attributes (旧 Import Assertions) Proposal (執筆時点でStage 3) に依存しており、以下のように書くことでCSSをパースさせ、その結果を読むことができます。

<script type="module">
  import styleSheet from "https://example.com/something.css" with { type: "css" };
  // styleSheet: CSSStyleSheet
</script>

この手順で生成されるCSSStyleSheetオブジェクトはconstructed style sheetであり、いかなる要素にも紐付いていません。取得したスタイルシートを文書に適用するには、 adoptedStyleSheets にスタイルシートオブジェクトを追加する必要があります。

読み込み中の動作

WebブラウザはHTML文書本体の構文解析を進めながら、関連するリソースの取得と処理を非同期で行います。このとき、文書は取得できた部分から順番に表示されていきます。

しかし、スタイルがまだ意図通りに当たっていない状態で <body> の描画が開始されてしまうと、大きなガタつきが起きてユーザー体験を悪化させる可能性もあります。そこでHTML仕様では描画のブロックという仕様が定義されています。これはざっくり言うと、特定のリソースが読み込まれていない間は文書を表示しないということを意図しています。

描画のブロックが起きるには以下の前提条件が必要です。

  • <head> のパース中に追加された要素に由来するリソースであること。
  • 所定のタイムアウトを超過していないこと。

また、不要な描画ブロックを防ぐため、描画を妨げるようなリソースは最小限に抑えられています。描画をブロックするリソースは概ね以下のように定められています。

  • そのリソースが描画に実際に必要であること。スタイルシートの場合、たとえばdisabled, title, mediaなどの属性により描画にただちに必要ないことが判明しているリソースは描画をブロックしません。
  • そのリソースのの重要性 (→ 重大リソース)。どのようなリソースが重大リソースにあたるかは現時点では正確には定義されていません。

シャドウDOMのスタイルシート

文書がスタイルシートのリストを持つのと同様に、シャドウルートもスタイルシートのリストを持っています。その規則は文書に対するものとほぼ同等です。

<style><link> などの要素に紐付くCSSStyleSheetは、高々1つの文書またはシャドウルートにしか適用されません。こういった要素のスタイルシートは、その要素に対応する (親をたどった先にあるノード) にのみ紐付けられるからです。

もし、同じCSSStyleSheetを文書とシャドウルート (あるいは複数のシャドウルート) に適用したければ、constructed style sheetをadoptedStyleSheetsに追加するという方法を取る必要があります。あるいは、同じソースから複数のCSSStyleSheetを作るほうが簡単かもしれません (同じURLにあるCSSを複数の <link> 要素から参照するなど)。

ただし、文書に紐付いたCSSStyleSheetであっても、シャドウルート内の要素のスタイルに影響を与えることはあります。これは継承や、シャドウDOMを考慮したセレクタの影響によって生じます。


脚注
  1. CSS Modules とは異なります。いずれもCSSをJavaScriptからimportするという目的は共通していますが、利用環境やインポートの挙動は全く異なります。 ↩︎

Discussion