内包要素の数が変動しても僕には擬似クラスと間接セレクタがある

CSS Property Advent Calendar 2013 4日目のエントリです。

昨日、げこたんさんに BEM Advent Calendar を手伝ってもらったら、

と言われてしまったので2回目を登録しました。

要件

「データがある時はリンクを出したい」などのニーズで内包要素の数がページによって増えたり減ったりすること、けっこうありますよね。それが普通のテキストリンクではなくタブだとかサムネだとかでレイアウトにも関わるとき、要素の数によってスタイルを切り分けなきゃいけないわけですが、タブが2つの時には.tabs2。5つの時には.tabs5とかいちいちクラス付与させるのも面倒くさいわけです。

どういうことかというと、このようなHTMLがあったとして、

<div class="tabs">
  <a class="tabs__item">タブ1</a>
  <a class="tabs__item">タブ2</a>
  <a class="tabs__item">タブ3</a>
</div>

.tabs__itemがページによっては2つだったり5つだったりする時、という想定です。図示すると以下のような感じです。

タブの数が異なるけど親要素内に収まるようにしたい

親の.tabsは幅が決まっていて、操作性を考えて.tabs__itemの大きさは常にいっぱいまで広げておきたいな〜とかいう時、擬似クラスと間接セレクタを使うと内包要素の数ごとにクラスを作らなくて済むようになります。

擬似クラスと間接セレクタの合わせ技

.tabs{
  box-sizing: border-box;
  padding: 0 10px;
  width: 320px;
}

.tabs__item{
  box-sizing: border-box;
}

/* 2 tab */
.tabs .tabs__item:first-child:nth-last-child(2),
.tabs .tabs__item:first-child:nth-last-child(2) ~ .tabs__item {
  width: 50%;
}

/* 3 tab */
.tabs .tabs__item:first-child:nth-last-child(3),
.tabs .tabs__item:first-child:nth-last-child(3) ~ .tabs__item {
  width: 33.3%;
}

/* 4 tab */
.tabs .tabs__item:first-child:nth-last-child(4),
.tabs .tabs__item:first-child:nth-last-child(4) ~ .tabs__item {
  width: 25%;
}

/* 5 tab */
.tabs .tabs__item:first-child:nth-last-child(5),
.tabs .tabs__item:first-child:nth-last-child(5) ~ .tabs__item {
  width: 20%;
}

ミソは:first-child:nth-last-child(n)の部分です。:first-childは「最初の子要素」という意味ですね。:nth-last-child(n)は「最後からn番目の子要素」という意味です。:nth-last-child(n)は、たとえば子要素が全部で3つあった時、最初の子要素が:nth-last-child(3)、二番目の子要素が:nth-last-child(2)、三番目の子要素は:nth-last-child(1)となっていきます。先のHTMLで言うと、

<div class="tabs">
  <div class="tabs__item">:nth-last-child(3)</div>
  <div class="tabs__item">:nth-last-child(2)</div>
  <div class="tabs__item">:nth-last-child(1)</div>
</div>

こうですね。.tabs__itemが5つの時は

<div class="tabs">
  <div class="tabs__item">:nth-last-child(5)</div>
  <div class="tabs__item">:nth-last-child(4)</div>
  <div class="tabs__item">:nth-last-child(3)</div>
  <div class="tabs__item">:nth-last-child(2)</div>
  <div class="tabs__item">:nth-last-child(1)</div>
</div>

こういうことになります。で、これに:first-childを重ねるとどうなるかというと、:first-childは子要素の数にかかわらず1番目にマッチするので

<div class="tabs">
  <div class="tabs__item">:nth-last-child(3):first-child</div>
  <div class="tabs__item">:nth-last-child(2)</div>
  <div class="tabs__item">:nth-last-child(1)</div>
</div>

<div class="tabs">
  <div class="tabs__item">:nth-last-child(5):first-child</div>
  <div class="tabs__item">:nth-last-child(4)</div>
  <div class="tabs__item">:nth-last-child(3)</div>
  <div class="tabs__item">:nth-last-child(2)</div>
  <div class="tabs__item">:nth-last-child(1)</div>
</div>

ということになります。擬似クラスの組み合わせで子要素の数に応じてスタイルを切り分けることができました。

しかし、.tabs .tabs__item:first-child:nth-last-child(5)と指定しただけでは.tabs__itemが5つのときの最初の要素にしかスタイルが適応されません。そこで間接セレクタ~を使います。

例えば.box1 ~ .box2{...}とすると、.box1と兄弟関係にある弟要素の.box2がスタイル適用対象となります。

.box1 ~ .box2{
  background: tomato;
}
<div class="box2">テキスート.box2</div>
<div class="box1">テキスート.box1</div>
<div class="box2">テキスート.box2</div>
<div class="box2">テキスート.box2</div>
<div class="heading">ヘッディーン.heading</div>
<div class="box2">テキスート.box2</div>

上記のようなHTMLの場合、3行目の.box2からが適応対象です。1行目の.box2.box1の兄弟関係にありますが、.box1より先に記述されており弟要素でないのでスタイル適応外となります。また、途中に.headingが挟まっていすが、そのあとに出てくる.box2は先の.box1の弟要素なのでスタイルは適応されます。 CodePenにデモを置いたので参考にどうぞ。

間接セレクタで間にある兄弟要素をカバーしたら勝ったも同然

.tabsのHTMLに戻りましょう。

.tabs .tabs__item:first-child:nth-last-child(5) ~ .tabs__item

これを見ると、最後から5番目かつ最初の.tabs__itemの兄弟要素な.tabs__itemとなります。これだと.tabs__item:first-childにはスタイルが適応されないので、.tabs__item:first-child:nth-last-child(5)をグルーピングして同じルールセットを適用させます。

.tabs .tabs__item:first-child:nth-last-child(5),
.tabs .tabs__item:first-child:nth-last-child(5) ~ .tabs__item{...}

というわけです。:nth-last-child(n)の値を変えれば内包要素が何個になっても対応できます。便利ですね!

で、:nth-last-child(n)~に対応しているブラウザは

  • Internet Explorer 9+
  • Firefox 4+
  • Google Chrome 9+
  • Safari 5+
  • Opera 10+

モダンブラウザなら戦えそうですね!


今気づいたんですけど、CSS Property Advent CalendarなのにCSSセレクタのことを書いてしまいました。まぁいいかな?と思ってAdvent Calendarを改めて見てみると......。

CSSのプロパティに関することだったら何でもOKです!

セレクタ、ダメです!

5日目は maechabinさんです。よろしくお願いします。