WWW Watch

特定の範囲内にのみスタイルを適用できる scoped 属性

HTML5 で追加された scoped 属性は特に目新しい属性ではありませんが、ここまで正式に対応したブラウザが存在しなかったため、使う機会がありませんでした。ところが、ここにきて Firefox Nightly が対応したそうなので、ちょこっと試してみます。

HTML5HTML5 で style 要素に追加された scoped 属性は特に目新しい属性ではありませんが、ここまで正式に対応したブラウザが存在しなかったため、使う機会がありませんでした。ところが、ここにきて Firefox Nightly Builds が対応したそうなので、ちょこっと試してみます。

scoped 属性付きの style 要素内に記述されたスタイルは、文書全体ではなく、その style 要素の親要素をルートとするサブツリーに対してのみ適用されます。例えばページ内にちょっとした要素ブロックを追加したいんだけど、全体の CSS ファイルにはいちいち手を加えたくないな……なんて時に役に立つかもしれません。

仕様書から抜粋すると、scoped 属性とは具体的には下記のような感じ。

The scoped attribute is a boolean attribute. If present, it indicates that the styles are intended just for the subtree rooted at the style element's parent element, as opposed to the whole Document.

If the scoped attribute is present and the element has a parent element, then the style element must be the first node of flow content in its parent element other than inter-element whitespace, and the parent element's content model must not have a transparent component.

4.2.6 The style element -> The scoped attribute : HTML5 から引用

前半は上で書いたとおり、この属性があるとスタイルは文書全体ではなく、その style 要素の親要素をルートとするサブツリーに対してのみ適用されるよっていう話。

後半は scoped 属性が付与された style 要素が出現する位置についてですが、scoped 属性を持つ style 要素は、ホワイトスペースを除く、親要素内のフローコンテンツの最初のノードでなければいけないっていうのと、親要素がコンテンツモデルとして 「transparent content (トランスペアレント・コンテンツ)」 を持っちゃダメってルールになります。これはサンプルを挙げて後述します。

style 要素の仕様部分にも同じようなことが書いてあります。

  • If the scoped attribute is absent: where metadata content is expected.
  • If the scoped attribute is absent: in a noscript element that is a child of a head element.
  • If the scoped attribute is present: where flow content is expected, but before any other flow content other than inter-element whitespace, and not as the child of an element whose content model is transparent.

4.2.6 The style element : HTML5 から引用

style 要素が使えるのは……

  • scoped 属性がなければ、「Metadata content (メタデータ・コンテンツ)」 が期待される場所
  • scoped 属性がなければ、head 要素の子となる noscript 要素の中
  • scoped 属性が付与されていれば 「Flow content (フロー・コンテンツ)」 が期待される場所。ただし、ホワイトスペースを除くあらゆるフロー・コンテンツより前で、かつ 「transparent content (トランスペアレント・コンテンツ)」 の子要素でない場所

具体的にどうなるのか

ということで、scoped 属性に対応した Firefox Nightly Builds と、現状での最新版 Firefox 18.0.2 で上記で紹介した modest 記事内でもリンクされている下記のデモページを見てみましょう。

使用した Nightly Builds は下記のバージョン

Firefox Nightly Builds のバージョン

デモページのソースコードは下記です。

<!DOCTYPE html>
<meta charset=utf-8 />
<title>Scoped style support</title>
<h1>Scoped style support</h1>
<style>
  div {
    border: 2px solid blue;
    margin: 10px;
  }
</style>
<div>
  <div>
    <style scoped>
      div {
        border: 2px solid red;
      }
      :scope {
        background: #CCC;
      }
    </style>
    <div>foo</div>
  </div>
</div>

まずは未対応の Firefox で見た場合

body 要素内に書かれた style 要素内の指定 (scoped 属性付きのやつ) が、head 要素内 (ソース上は head 要素が省略されていますのでわかりにくいかもしれませんが) に記述されたスタイルを上書きし、すべての div 要素の枠線が赤色になってます。これは仕様通りのカスケード処理がされた結果です。また、:scope 擬似クラスも無視されているのがわかります。

scoped 属性に未対応の Firefox (18.0.2) での表示

一方 Nightly Builds での表示

下の画像が Nightly Builds での表示ですが、scoped 属性が付与された style の親要素のサブツリーのみに、指定されたスタイルが適用されているのがわかります。1番最初の div 要素は head 要素内のスタイル指定通り、枠線が青になりました。

scoped 属性に対応した Firefox (21.0a1) での表示

:scope 擬似クラスについても簡単に

:scope 擬似クラスは、Selectors Level 4 で定義されていますが、ある要素を基点に、その子孫要素を取得するための疑似クラスです。

詳しくは、

あたりを参照。

The :scope pseudo-class represents any element that is in the contextual reference element set. This is is a (potentially empty) explicitly-specified set of elements, such as that specified by the querySelector() call in [SELECTORS-API2], or the parent element of a scoped <style> element in [HTML5], which is used to "scope" a selector so that it only matches within a subtree.

If no contextual reference element set is given, :scope is equivalent to :root. Specifications intending for this pseudo-class to match specific elements rather than the document's root element must define a contextual reference element set.

7.5. The contextual reference element pseudo-class :scope : Selectors Level 4 から引用

正しい使い方の例と、間違った例

前述した通り、scoped 属性が付いた style 要素は 「Flow content (フロー・コンテンツ)」 が期待される場所、ただし、ホワイトスペースを除くあらゆるフロー・コンテンツより前で、かつ 「transparent content (トランスペアレント・コンテンツ)」 の子要素でない場所でしか使えません。具体的なサンプルソースでそれを示してみます。

まずは正しい例

フロー・コンテンツが期待される場所 (div 要素のコンテンツモデルはフロー・コンテンツ) で、しかも最初のフロー・コンテンツとして記述されています。

<div>
  <div>
    <style scoped="scoped">
      div {
        border: 2px solid red;
      }
    </style>
    <div>foo</div>
  </div>
</div>

間違った例

ここからは間違った例を。

例えば、style 要素の前に別のフロー・コンテンツが入っちゃってるとか……

<!-- ↓これは間違った例 -->
<div>
  <div>
    <div>foo</div>
    <style scoped="scoped">
      div {
        border: 2px solid red;
      }
    </style>
    <div>bar</div>
  </div>
</div>

p 要素のコンテンツモデルは、フレージング・コンテンツだから、p 要素内には書けないよとか……

<!-- ↓これは間違った例 -->
<div>
  <p>
    <style scoped="scoped">
      a {
        color: red;
      }
    </style>
    <a href="hoge.html">hoge</a>
  </p>
</div>

トランスペアレント・コンテンツの子要素になる場所で使っちゃダメとか。そんな感じ。

<!-- ↓これは間違った例 -->
<section>
  <a href="hoge.html">
    <div>
      <style scoped="scoped">
        div {
          border: 2px solid red;
        }
      </style>
      <div>foo</div>
    </div>
  </a>
</section>

使用する場合は気を付けましょう。

その他、補足

@規則の扱い

scoped 属性を付与した style 要素内で @規則を使った場合は、その内容も適用範囲が限定されます。例えば、下記のような記述をした場合。

<div>
  <style scoped="scoped">
    @font-face{
      font-family: example;
      src: url(example.eot);
    }
  </style>
  <div>foo</div>
</div>

読み込まれたフォントが適用されるのは、文書全体ではなく、「foo」 の部分に対してのみということになります。

ちなみに、scoped 属性を付与した style 要素内における @page 規則の指定は無効です。

フォールバック

scoped 属性に対応しないブラウザのために、下記のようにセレクタを指定して書いた方が安全かもね。

<div id="hoge">
  <style scoped="scoped">
    #hoge div {
      border: 2px solid red;
    }
  </style>
  <div>foo</div>
</div>

ということで、使い方によっては便利な scoped 属性。機会があればうまく利用してみてください。

関連エントリー

Recent Entry

全ての記事一覧を見る

Hot Entry

逆引きおすすめエントリー