2011年9月29日、CSS3セレクタ仕様の勧告と同時に、Selectors Level 4のFPWDが公開されました。Level 4では誰もが考えた「親セレクタ」をはじめ、より高機能なセレクタが提案されています。このノートでは、Selectors Level 4仕様で新しく追加された機能について紹介します。
公式な草案にリンクしている部分はあるものの、このノートが参照する仕様書はEditor's Draftの情報になります。このため、WDと記述が食い違っている可能性があります。
まだ書いている途中だったりします。このノートの変更はGitHubのレポジトリからたどってください。
Selectors Level 4 は、Selectors Level 3仕様の上位にある新しいセレクタ仕様です。Level 3では:nth-child()
など便利なセレクタが数多く追加されましたが、Level 4仕様ではより便利なセレクタや、新しい仕組みが検討されています。
仕様が初期の段階ですから、現在の仕様書にある機能がそのうちなくなる可能性があります。反対に、現在の仕様書にない新しいセレクタが追加される可能性もあります。
ふだん「セレクタ」と読んでいるものですが、仕様書ではその種類や用語が細かく定義されています。
これらを厳密に区別して使う機会はあまりないと思いますが、覚えておくと仕様書を読むときに便利です。
?selector
記法FPWDが出てすぐ、$
から?
になりました。(参照)
CSS4セレクタでは、念願かなって要素の「親」を指定できるようになりました。
ただし、「親セレクタ」が導入されたわけではありません。むしろ、もっと便利な機能の、ひとつの応用例として使えるようになっています。
とりあえず、何かの親にスタイルを当ててみましょう。
ul > ?li > a { list-style: none; }
li
の前にさりげなく?
とあるのがわかるでしょうか。これがCSS4で導入された新機能です。
セレクタによって表される要素 (スタイルが適用される要素) を、セレクタ仕様では「対象 (subject)」と呼びます。これまでのレベルでは、各セレクタのうち最後の要素が対象となっていました。
CSS4では、対象としたい合成セレクタの前にドル記号(?
)をつけることで、この対象を変更できるようになりました。ですので、ul > ?li > a
とすると、「子にa
を持つli
要素」にスタイルを当てられるわけです。
:matches()
CSS4では、:matches()
という擬似クラスが導入されました。引数に複数のセレクタを書くと「そのうちのどれか」にマッチします。
カンマと何が違うんだという話ですが、ネストした場合にとても楽です。HTML5のsectioning content (section
, article
, aside
, nav
)とh1
の組み合わせを考えてみましょう。
/* トップレベルの見出し */
h1 {
font-size: 240%;
font-weight: bold;
}
/* セカンドレベルの見出し */
section h1, article h1, aside h1, nav h1 {
font-size: 210%;
}
/* サードレベルの見出し */
section section h1, section article h1, section nav h1, section aside h1,
article section h1, article article h1, article nav h1, article aside h1,
aside section h1, aside article h1, aside nav h1, aside aside h1,
nav section h1, nav article h1, nav nav h1, nav aside h1 {
font-size: 180%;
}
/* もういや…… */
ひどくなります。aside
中にarticle
とか、nav
中にnav
とかどれだけあるんだよと思いもしますが、それはさておいて。ここで:matches()
が力を発揮します。
/* トップレベルの見出し */
h1 {
font-size: 240%;
font-weight: bold;
}
/* セカンドレベルの見出し */
:matches(section, article, aside, nav) h1 {
font-size: 210%;
}
/* サードレベルの見出し */
:matches(section, article, aside, nav)
:matches(section, article, aside, nav) h1 {
font-size: 180%;
}
/* 4th... */
:matches(section, article, aside, nav)
:matches(section, article, aside, nav)
:matches(section, article, aside, nav) h1 {
font-size: 150%;
}
/* こんな感じで続く! */
こんな風に、かなり見通しが良くなります。
:matches()
はもともと、MozillaがGeckoに導入した:-moz-any()
という独自拡張のセレクタが元になっています。このセレクタによって、ul
, ol
, dir
などの複雑なネストや、上記のようなHTML5のセクションと見出しのスタイル付けがとても見やすいものになりました。
:-moz-any()
はその後WebKitにも:-webkit-any()
として導入され、UAスタイルシートでHTML5のセクションと見出しのスタイルづけに利用されています。
なお、CSS4では引数内のセレクタに結合子が使えないという制限があります。また、:matches()
や:not()
をネストさせることはできないともされています。
todo: もっと実用的な例がないか探す。
:not()
の引数とセレクタリストCSS3の:not()
は、ひとつのセレクタしか指定できませんでした。また、擬似クラスや結合子を指定することもできませんでした。CSS4の:not()
では、:matches()
に合わせ、引数に複数のセレクタをとれるようになりました。また、結合子は使えないものの、擬似クラスの利用が可能になっています。
:any-link
:any-link
は「ハイパーリンクのソースアンカーとして機能する要素」にマッチします。なんのこっちゃという話ですが、、ざっくりいうと:link
または:visited
にマッチする擬似クラスです。:link, :visited
と同じです。リンクだけを選択したいときに、a:link, a:visited
としなくて済むわけです。
「そんなのa
でいいじゃん」という話もあるかもしれませんが、href
属性のない (ハイパーリンクが結ばれてない) a
要素は「リンクになるかもしれないプレースホルダ」を表すので、ハイパーリンクのアンカーではありません。ブラウザの実装も、:link
もしくは:visited
は<a>blah</a>
にマッチしません。
この:any-link
は、MozillaがGeckoに組み込んだ:-moz-any-link
というセレクタがもとになっています。同じ働きをするものがWebKitでは:-webkit-any-link
として実装されています。また、Operaでも内部的にそういったセレクタを持っているようです。
:local-link
Webサイトは階層構造をとっていることが多いですが、「現在地」のリンクや「下層」のリンクについてスタイルを変えたいということがあるかと思います。提案中の:local-link
は、それを実現する擬似クラスです。
とりあえず、仕様書の例を拾ってみます。ナビゲーションにある、現在地と同じリンクの下線を無効にする場合、こう書きます。
nav :local-link { text-decoration: none; }
:local-link
には関数表記もあります。中に数値 (0以上の自然数) を書くことで、特定階層以下のリンクにマッチさせることができます。たとえば、:local-link(1)
と書くと、ルートディレクトリの子ディレクトリ以下へのリンクにマッチします。
/* /product/ やら /news/ やらへのリンクにマッチする */
:local-link(1) { ... }
注意したいのが、サブディレクトリも含めてしまうことでしょうか。
http://example/
以下に:local-link(2)
というセレクタがあるとします。このとき、http://example/foo/bar/
へのリンクにマッチするのは当然ですが、前方マッチですから、指定した階層以下のリンクにもマッチしてしまいす。特定のディレクトリ直下のみへのリンクを指定したい場合は、それより下階層へのリンクにマッチするセレクタを書き、宣言を上書きするしかありません。
仕様書では、:not()
と組み合わせ、外部リンクにスタイルを当てる例も紹介されています。
:not(:local-link(0)) { text-decoration-style: dashed; }
注意としては、「内部リンク」の定義でしょうか。仕様書では“domain”とありますが、ホストでのマッチではなくoriginのマッチとして動作するようです。サブドメインなどは「外部リンク」として扱われてしまいます。
ごくたまに、セレクタでハマることがあります。属性値が大文字と小文字の混在なのに、セレクタは全部小文字で書いていて適用されなかったケースです。
HTMLの定義では、セレクタはいくつかの属性を除いて、基本的に属性値の大文字小文字を区別します。class
やid
などは、大文字小文字を間違えるとマッチしないわけです (Quirksモードを除く)。
ただ、大文字小文字を含めてマッチさせたいケースがあるかもしれません。Selectors 4では、属性セレクタの書式が拡張され、大文字小文字を区別しない属性値のマッチングが可能となりました。
elem[attr="whatevercase" i] { ... }
属性セレクタの閉じ括弧の前にi
と記述すると、属性値のマッチングで大文字と小文字を問わないようになります。この場合はattr="whatevercase"
でもattr="WHATEVERCASE"
でもattr="whateverCase"
でもマッチします。
なお、ここでの「大文字と小文字」はASCIIの範囲 ([a-z]
と[A-Z]
) なので、その範囲外は対象外となります。
:nth-match()
:nth-child()
や:nth-of-type()
は強力な擬似クラスですが、これらは要素にしかかかりません。「class=foo
が与えられた<li>
のうち3番目」を選択しようとli.foo:nth-child(3)
なんて書いてもマッチしませんし、マッチしたとしても間違っています。これは、「3番目の<li>
要素がclass=foo
を持っているか」という条件を示しているからです。
そんな小難しいことはおいといて、やっぱり要素以外でももっと細かい条件を指定したい場合があります。Selectors 4では:nth-match()
と:nth-last-match()
という擬似クラスが導入され、これに対応しました。
構文は:nth-match(an+b of selector-list)
となっています。
/* 3番目のli.fooにスタイルをつける */
li:nth-match(3 of .foo) {
...
}
関数表記なのにやたらと記述的で面白いですね。
:scope
セレクタ仕様はCSSだけに限定されないと最初に書きましたが、この:scope
擬似クラスはそれをよく示す例で、今のところSelectors API Level 2での利用のみが仕様書に記されています。
Selectors API (Level 1)では、ある要素を基点に、その子孫要素を取得することができなかったのです。さらに、querySelector()
やquerySelectorAll()
は、メソッドの呼び出し元がElement
であったとしても、セレクタの評価はdocument
からと、理解しがたい仕様でもありました。
<ul>
<li><a>text</a>
</ul>
<script>
var li = document.querySelector('li');
var re = li.querySelectorAll('ul a');
console.log(re) // <a>が返ってくる
</script>
これについて、Selectors API Level 2では、querySelector()
, querySelectorAll()
から文脈を限定したマッチングを行えるようにするために:scope
という擬似クラスを導入しました。Selectors Level 4で定義されている:scope
は、これが移管されたものです。
// jQuery
$(elem).find('.section .box')
// Selectors API Level 2
elem.querySelectorAll(':scope .section .box')
/for/
combinator
:current
, :past
, :future
:column()
, :nth-column()
, :nth-last-column()