debiruはてなメモ

はてなブログの HTML が Invalid なの、わたし、気になります

CSS の疑似要素に疑似クラスを付ける話

経緯

https://sugamocss.slack.com/ で「::before:hover みたいなのが効かないんだけど仕様だっけ」という話が出たのでメモ。

sugamocss は2009年から2013年まで東京で定期的に開催されていた Web フロントエンドな人たちが集まる Sugamo.css という勉強会の名称です。

問題

「::before:hover という記述はできるか」(疑似要素に疑似クラスを指定できるか)

a 要素じゃなくてもいいですが、とりあえず a 要素で試してみます。

<div class="mod-experiment">
  <a class="test-anchor" href="#sect-2">(a 要素内容)リンク文字列</a>
</div>

/* a に hover したら ::before を黄色に */
.mod-experiment .test-anchor:hover:before {
  background: #ff0;
}
/* ::before に hover したら ::before を赤にして幅を広げる */
.mod-experiment .test-anchor:before:hover {
  width: 200px;
  background: #f00;
}

「a::before:hover と記述できる」のであれば、::before にオンマウスしたときに赤くなって幅が広がるはずですが、多分そうなるブラウザはないと思います。黄色になりますよね。

解説

CSS2 (2.1) の仕様では、疑似要素に対して疑似クラスは指定できないことになっています。

5.10 Pseudo-elements and pseudo-classes
Pseudo-classes are allowed anywhere in selectors while pseudo-elements may only be appended after the last simple selector of the selector.

(参考)http://momdo.github.io/css2/selector.html#pseudo-elements

(拙訳)疑似クラスはセレクタ内のどこにでも書くことができるが、一方で、疑似要素はセレクタ内の最後にある「単体セレクタ」の後にしか書くことができない。

あ、ちなみに「(参考)」として日本語訳の文書へのリンクを示していますが、これは当該部分の訳を見るためではなく、その周辺(拙訳として訳した部分以外)を日本語で読みたい人向けのものです。

5.2 Selector syntax
A simple selector is either a type selector or universal selector followed immediately by zero or more attribute selectors, ID selectors, or pseudo-classes, in any order.

(参考)http://momdo.github.io/css2/selector.html#selector-syntax

(拙訳)「単体セレクタ」は、タイプセレクタまたは全称セレクタのいずれかの直後に、0個以上の属性セレクタ、IDセレクタ、または疑似クラスが任意の順序で続いたものである。

つまり、::before などの疑似要素は(セレクタ内の最後にある)単体セレクタの後にしか記述できなくて、「div」「.hoge「a:hover」「div.hoge:hover」のようなものを単体セレクタという、ということですね。(あまり書きませんが「div:hover.hoge」と書いてもいいのですね。)

セレクタ内の最後にある単体セレクタ」の後ろにしか書けないので、「div.hoge a:hover::before」はよいですが、「div.hoge::before a」のようには書けません。

「単体セレクタ」という用語について

"simple selector" を「単体セレクタ」と訳していますが、これは既に存在する日本語訳を参考にしました。

どう訳すかは問題ではないのですが、この単体セレクタの意味について、CSS2 と CSS3 で定義が異なっています。

1.3. Changes from CSS2
the list of basic definitions (selector, group of selectors, simple selector, etc.) has been changed; in particular, what was referred to in CSS2 as a simple selector is now called a sequence of simple selectors, and the term "simple selector" is now used for the components of this sequence

(参考)http://standards.mitsue.co.jp/resources/w3c/TR/css3-selectors/#changesFromCSS2

(拙訳)セレクタセレクタグループ、単体セレクタなどの基本的な定義が変更された。特に CSS2 で「単体セレクタ」と呼ばれていたものは今では「単体セレクタシーケンス」と呼ばれるようになり、そして「単体セレクタ」は単体セレクタシーケンスの構成要素を指す語として使われるようになった。

単体セレクタ (simple selector) という語を使うときは、CSS2 での定義なのか、CSS3 での定義なのかを明確にしないと混乱が生じる可能性があるため注意しましょう。

CSS3 の仕様

CSS3 (Selectors Level 3) でも CSS2 と同様、疑似要素に疑似クラスを指定することはできません。

7. Pseudo-elements
Only one pseudo-element may appear per selector, and if present it must appear after the sequence of simple selectors that represents the subjects of the selector. Note: A future version of this specification may allow multiple pseudo-elements per selector.

(参考)http://standards.mitsue.co.jp/resources/w3c/TR/css3-selectors/#pseudo-elements

(拙訳)疑似要素はセレクタ毎に1つだけ書くことができ、もし疑似要素が書かれる場合には「セレクタの対象」を表す「単体セレクタシーケンス」の後に書かなければならない。

(拙訳)Note: 将来のバージョンの本仕様では、セレクタ毎に複数の疑似要素を書くことができるようになるかもしれない。

4. Selector syntax
The elements of a document tree that are represented by a selector are the subjects of the selector.

(参考)http://standards.mitsue.co.jp/resources/w3c/TR/css3-selectors/#subject

(拙訳)セレクタによって表されるドキュメントツリーの要素は、「セレクタの対象」である。

表現が変わっていますが結局は CSS2 の仕様書で言っていることと同じですね。「div .hoge a:hover」のようなセレクタ内の最後にある単体セレクタシーケンス(CSS2 でいう単体セレクタ)の後にしか記述できないということです。つまり ::before を書こうと思ったら「div .hoge a:hover::before」の位置しか書けないということです。

なお、"Note:" として「将来のバージョンの本仕様では」と書かれているのは、CSS3 の仕様の中でも現在「草案」段階である CSS3 Generated and Replaced Content Module が策定された場合のことを指していると認識しています。この話は後述します。

CSS4 の仕様

前セクションを「CSS3 の仕様」としたので「CSS4 の仕様」と書きましたが、ここで言いたいのは "Selectors Level 4" についてです。"CSS4" と "Selectors Level 4" という表現の違いが表す意味については http://myakura.github.io/n/selectors4.html#about-selectors4 が参考になります。

既に感づいている方がいるかもしませんが、先ほどから「構文的に疑似要素が書ける位置」について言及していて「疑似要素に疑似クラスが適用できるのか」という考察から逸れてしまっていました。

9.1. The Pointer Hover Pseudo-class: :hover
The :hover pseudo-class can apply to any pseudo-element.

(参考)http://www.hcn.zaq.ne.jp/___/WEB/selectors4-ja.html#the-hover-pseudo

(拙訳):hover 疑似クラスは任意の疑似要素に対して適用できる。

3.6.3. Pseudo-classing Pseudo-elements
A pseudo-element may be immediately followed by any combination of the user action pseudo-classes, in which case the pseudo-element is represented only when it is in the corresponding state.
EXAMPLE 6
For example, since the :hover pseudo-class specifies that it can apply to any pseudo-element, ::first-line:hover will match when the first line is hovered.

(参考)http://www.hcn.zaq.ne.jp/___/WEB/selectors4-ja.html#pseudo-element-states

(拙訳)疑似要素は、直後に「ユーザアクション疑似クラス」の任意の組み合わせが続いてもよく、その場合には、その疑似クラスに対応する状態であるときに限った疑似要素が(そのセレクタで)表される。

(拙訳)例えば、:hover 疑似クラスは任意の疑似要素に対して適用できると明示されているので、「::first-line:hover」は ::first-line が hover された場合にマッチすることになる。

このように仕様書を読んでみると、CSS3 (Selectors Level 3) では ::before に対して :hover を適用することはできないことになっていますが、一方で、Selectors Level 4 では「疑似要素に :hover 疑似クラスを適用できる」ことになっています。とはいっても、2015年現在、Selectors Level 4 の仕様は勧告されておらずブラウザ (UserAgent) が対応しているわけでもないので、この仕様の恩恵を受けることはできません。

なお、「ユーザアクション疑似クラス」については、同仕様書9. User Action Pseudo-classes に記述されています。

Firefox の疑似要素に対する疑似クラス

Mozilla では2013年10月に「疑似要素に対して疑似クラスが動作するよう実装してほしい」という要望が報告されました。

Bug 922669 - Implement support for the :hover user action pseudo-class on pseudo-elements

この報告レポート(末尾の2014年9月のコメント)を見ると、2014年3月にリリースされた Firefox 28 で対応しているように読み取れますが、実際には対応していませんでした。このことは前述のレポートがクローズされた直後に、新たなレポートによって報告されています。

Bug 1122965 - :hover pseudo-class doesn't work on pseudo-elements

まとめ

  • CSS2, CSS3 仕様ともに ::before などの疑似要素に対して :hover 疑似クラスを付けることはできません。
  • CSS4 (Selectors Level 4) では、これができると明記されています。
  • 2015年9月現在、疑似要素に対する疑似クラスの適用をサポートしているブラウザはありません。

補足

本題とは少し逸れることを書いておきます。

疑似と擬似

技術的な話からガラッと変わって国語の話です。

仕様書の翻訳や日本語の文書に多く現れる「疑似」と「擬似」という漢字表記の揺れ(手偏の有無)ですが、普段「ぎじ」を漢字で書く際に違いを意識されている方はどのくらいいるでしょうか。

結論から言うと、手偏のある「擬似」は本来正しくなく、手偏のない「疑似」の誤用が広まって使われるようになったという話ですが、この漢字表記について、逆に、手偏のある「擬似」を「誤用であるから修正すべきだ」と主張する人はどのくらいいるでしょうかね。

「擬似」と書かれていてもわざわざ指摘するようなことはしませんが、この話題について、調べた限りでは前述の説が正しいと理解しているので、この記事でも「疑似」という漢字表記に統一しています。

コロンの個数 - 疑似クラスと疑似要素

before 疑似要素について、「:before」とコロン1個で書かれたり「::before」とコロン2個で書かれたりすることがあります。この意味の違いは、ここまで読まれる方であれば理解されていることと思います。

CSS1 および CSS2 では、疑似クラス、疑似要素というものが定義されていますが、文法上は「div:hover」「div:before」と記述することになっていました。これがコロン1個の由来です。

http://www.w3.org/TR/CSS2/selector.html#pseudo-elements

CSS3 では、「疑似クラス」と「疑似要素」を区別するために、疑似要素についてはコロンを2個記述するように仕様が変わりました。これがコロン2個の由来です。ただし、CSS2 との互換性のために「CSS2 に存在する疑似要素に限っては、コロン1個でも解釈できる」と定められています。

10. The grammar of Selectors
pseudo
  /* '::' starts a pseudo-element, ':' a pseudo-class */
  /* Exceptions: :first-line, :first-letter, :before and :after. */
  /* Note that pseudo-elements are restricted to one per selector and */
  /* occur only in the last simple_selector_sequence. */
  : ':' ':'? [ IDENT | functional_pseudo ]
  ;

疑似要素での single-colon (:before) の記述は非推奨

Selectors Level 4 では疑似要素の構文について次のように書かれています。

3.6.1. Syntax
Because CSS Level 1 and CSS Level 2 conflated pseudo-elements and pseudo-classes by sharing a single-colon syntax for both, user agents must also accept the previous one-colon notation for the Level 1 & 2 pseudo-elements (::before, ::after, ::first-line, and ::first-letter). This compatibility notation is not allowed any other pseudo-elements.
ISSUE 4
Tab proposes deprecating the old syntax and RECOMMENDing that authors not use it.

(参考)http://www.hcn.zaq.ne.jp/___/WEB/selectors4-ja.html#pseudo-element_syntax

(拙訳)CSS1 および CSS2 では、疑似要素と疑似クラスの両方で single-colon による構文に統一されていたため、ユーザエージェントは、以前の CSS1 および CSS2 の疑似要素である ::before, ::after, ::first-line, ::first-letter に対しては、コロン1個の記法も解釈するようにしなければならない。この互換性のための(疑似要素でのコロン1個の)記法は、(CSS3 で新たに定義された)他の疑似要素に対しては許容されない。

(拙訳)Tab 氏は(疑似要素でのコロン1個の)旧構文を仕様上で非推奨とすること、および作者がそれを使わないことを推奨するべきだと提案している。

実際、次の章に示すように CSS3 の double-colon に対応していないのは IE8 くらいです。IE8 を考慮しなくてもよい状況であれば、「div::before」のように、積極的にコロン2個の記法を使うようにしたいですね。

ブラウザの対応状況

https://developer.mozilla.org/en-US/docs/Web/CSS/::before#Browser_compatibility より引用

Browser:before support
(single colon)
::before support
(double colon)
Browser:before support
(single colon)
::before support
(double colon)
Chrome (Yes) (Yes)
Firefox (Gecko) 1.0 1.5
IE 8 9
Opera 4 7
Safari (WebKit) 4.0 4.0
Android (Yes) (Yes)
Firefox Mobile (Gecko) (Yes) (Yes)
IE Mobile Unknown 7.1
Opera Mobile Unknown Unknown
Safari Mobile Unknown 5.1

http://caniuse.com/#feat=css-gencontent も併せて確認するとよいかもしれません。

::before, ::after の拡張と ::outside 疑似要素

::before(2), ::before(3) とか ::outside という疑似要素を聞いたことがある方がいるかもしれません。これについて簡単に紹介しておきます。

この仕様については、次の文書を参照してください。

::before, ::after のネスト

::before や ::after 疑似要素の中に、::before や ::after 疑似要素を入れられるという仕様です。

div { content: 'A' }
div::before { content: 'B'; }
div::before::before { content: 'C'; }

これは、次のようになります。

,-----------------------.
| ,---------.           |
| | ,---.   |           |
| | | C | B | A         |
| | `---'   |           |
| `---------'           |
`-----------------------'

複数の ::before, ::after

1つの要素(::before, ::after 疑似要素も含む)に対して ::before や ::after を複数指定できるという仕様です。

div { content: 'A' }
div::before { content: 'B'; }
div::before(2) { content: 'C'; }

/* これは以下と同値
div { content: 'A' }
div::before(1) { content: 'B'; }
div::before(2) { content: 'C'; }
*/

これは、次のようになります。

,-----------------------.
| ,---. ,---.           |
| | C | | B | A         |
| `---' `---'           |
`-----------------------'

::outside 疑似要素

ある要素を包含するような疑似要素を生成できるという仕様です。

div { display: block; border: dashed; }
div::outside { display: block; border: dashed; }
div::outside(2) { display: block; border: dashed; }

これは、次のようになります。

,-----------------------.   <-- border of ::outside(2)
| ,-------------------. |   <-- border of ::outside
| | ,---------------. | |   <-- border of DIV
| | | DIV           | | |
| | `---------------' | |
| `-------------------' |
`-----------------------'

::outside と ::before, ::after の組み合わせ

::outside や ::before, ::after を組み合わせると、1つの「要素」から無数の「疑似要素」を生成できます。

span                     { content: "span"; }
span::before             { content: "B";    }
span::outside(1)         { display: inline; }
span::outside(1)::before { content: "A";    }
span::outside(2)         { display: inline; }
span::outside(2)::after  { content: "C";    }

これは、次のようになります。

,--------------------------------.   <-- border of ::outside(2)
| ,----------------------.       |   <-- border of ::outside(1)
| | ,---. ,-+---+------. | ,---. |   <-- border of span, ::before,
| | | A | | | B | span | | | C | |          and ::after boxes
| | `---' `-+---+------' | `---' |
| `----------------------'       |
`--------------------------------'

superior(優勢)な parent(親)と siblings(兄)

::before, ::after のネストと ::outside 疑似要素によって仮想的な親が、複数の ::before, ::after によって仮想的な兄という概念が登場します。仮想というと少し意味合いが変わってしまうので、邦訳に合わせて、ここでは "superior" を優勢と言うことにします。

  • superior parent (優勢親要素)
  • superior siblings (優勢兄要素)
  • superior (優勢要素:優勢親要素と優勢兄要素の総称)
superior parent (優勢親要素)
「::before::after」に対する「::before」です。
また、「::outside::before」に対する「::outside」です。
また、「div::outside」に対する「div」です。
また、「::outside(n)」に対する「::outside(n-1)」です。
superior siblings (優勢兄要素)
「::before(2)」に対する「::before(1)」です。
また、「兄の兄」は「兄」であるので、「::before(5)」に対する「::before(2)」も該当します。siblings が複数形であるのはこのためです。
なお、「div::before(1)」に対する「div」は優勢兄要素ではありません。優勢兄要素となるのは ::before または ::after 疑似要素に限ります。

スタイルの継承は「優勢親要素」から行われます。「div p::outside」と記述したような場合、レンダリングツリー上では「div > ::outside > p」の順になりますが、継承は「div > p > ::outside」の順になります。

より詳しく知りたい方は仕様書を参照ください(セクション冒頭のものと同じです)。

最後に

もともとこの辺に詳しいわけではなく、Slack で話題が出てから調べたのをまとめただけなので、不正確な情報を含んでいる可能性があります。

ここで書いていることに誤りがあればご指摘ください。

また、各種ブラウザの対応状況や仕様の経緯など、何か関連情報があればコメントでもくれるときっと嬉しいです。

Next debiru's HINT 「WebはWEBじゃない