CSSで日付範囲を選択する::nth-child(n of selector)を活用したスマートなUI実装術

CSSで日付範囲を選択する::nth-child(n of selector)を活用したスマートなUI実装術

CSSで日付範囲を選択する::nth-child(n of selector)を活用したスマートなUI実装術

Webサイトでホテルの予約や航空券の検索を行う際、カレンダーから「開始日」と「終了日」を選ぶUIは欠かせない要素だ。この「日付範囲の選択」を実装する場合、従来はJavaScriptを駆使して、選択された期間内のすべての要素に特定のクラスを付与する手法が一般的だった。

しかし、最新のCSSセレクタを活用すれば、JavaScriptの役割を最小限に抑えつつ、高度な範囲指定のスタイリングが可能になる。特に「:nth-child(n of selector)」という構文は、複雑な要素選択を劇的に簡素化する力を持っている。

この記事では、CSS-Tricksで紹介された手法を基に、最新のCSSセレクタを組み合わせてスマートな日付範囲セレクターを構築する方法を詳しく解説する。コードの保守性を高め、ブラウザの負荷を軽減する新しい実装アプローチを見ていこう。

:nth-child(n of selector) の基礎知識

:nth-child(n of selector) の基礎知識

まず、今回の実装の核となる「:nth-child(n of selector)」について理解を深めておこう。これはCSSの「擬似クラス」と呼ばれる機能の一つで、特定の条件に合う要素の中から、さらに順番を指定して選択できる強力なツールだ。

従来の :nth-child との違い

従来の :nth-child(n) は、「親要素から見て何番目の子要素か」を基準に判定していた。例えば .item:nth-child(2) と書いた場合、「2番目の子要素であり、かつ .item クラスを持っている要素」にスタイルが適用される。もし2番目の要素が別のクラスだった場合、何も選択されないという問題があった。

一方で、新しい :nth-child(n of .selector) 構文は、まず指定したセレクタ(この場合は .selector)に一致する要素だけをフィルタリングし、その抽出されたリストの中からn番目を選択する。これにより、間に別の要素が挟まっていても、特定のクラスを持つ要素だけを正確にカウントできるようになった。

フィルタリング機能の仕組み

この構文の最大のメリットは、動的に変化する状態に対しても柔軟に対応できる点だ。例えば、ユーザーがチェックを入れた要素だけを対象に「1番目のチェック済み要素」や「2番目のチェック済み要素」を指定できる。これは、日付範囲の開始点と終了点を特定する際に非常に役立つ仕組みだ。

通常の :nth-child(2) の場合
1. 項目(対象外)
2. 広告(2番目だがクラスが違うため不適合)
3. 項目(3番目なので不適合)
:nth-child(2 of .item) の場合
1. 項目(1番目)
2. 広告(無視される)
3. 項目(.item の中で2番目なのでヒット!)

このデモのように、特定の要素群(この場合は「項目」)だけを対象にして順番を数えられるのが、このセレクタの革新的な点だ。

カレンダーの基本レイアウトを作成する

カレンダーの基本レイアウトを作成する

日付範囲選択を実装するために、まずは土台となるカレンダーのレイアウトを準備する。CSS Grid(グリッドレイアウト)を使えば、カレンダーのような格子状の配置は驚くほど簡単に記述できる。

Grid Layoutによる7列配置

カレンダーは1週間が7日であるため、7つの列を持つグリッドを作成する。grid-template-columns:repeat(7, 1fr) と指定することで、親要素の幅を均等に7分割した列が自動的に生成される。これにより、日付の数字を順番に並べるだけで、自動的に適切な位置で改行されるようになる。

HTML構造の設計

HTML側では、各日付をリスト要素(<li>)として配置する。各日付の中には、チェック状態を管理するための <input type="checkbox"> を隠し要素として入れておく。ユーザーが日付をクリックした際に、このチェックボックスが切り替わる仕組みだ。

<ul id="calendar">
  <!-- 曜日の表示 -->
  <li class="day">月</li>
  <li class="day">火</li>
  <!-- ...土日まで -->

  <!-- 日付の表示 -->
  <li class="date">01<input type="checkbox" value="01"></li>
  <li class="date">02<input type="checkbox" value="02"></li>
  <!-- ...31日まで -->
</ul>

CSSでは、この #calendar に対して display:grid を適用し、曜日と日付が綺麗に整列するように調整する。各日付(.date)は、ユーザーがクリックしやすいように十分なサイズと適切なパディングを持たせておくことが重要だ。

JavaScriptとCSSの役割分担

JavaScriptとCSSの役割分担

日付範囲の選択において、すべての処理をCSSだけで完結させることは現在の仕様では難しい。チェックボックスの「2つまでしか選択させない」といったロジックや、3つ目が選ばれた際の挙動制御にはJavaScriptが必要となる。しかし、ここで大切なのは「役割の最適化」だ。

チェック状態の制御ロジック

JavaScriptの主な仕事は、ユーザーのクリックに応じて checked 属性を適切に操作することだ。CSS-Tricksの記事で紹介されているロジックでは、新しく日付がクリックされた際、既存の選択範囲との位置関係を判定し、開始日または終了日を更新する処理を行っている。

ここで :nth-child(n of selector) がJS内でも威力を発揮する。querySelector メソッドでこのセレクタを使うことで、「現在チェックされている要素のうち、1番目のもの」を :nth-child(1 of :has(:checked)) として直接取得できるのだ。わざわざループを回してインデックスを探す手間が省ける。

CSSセレクタによる要素の特定

JS側で「範囲が選択された」と判断した際、親要素であるカレンダーに isRangeSelected といったクラスを付与する。これ以降の「範囲内の要素を青く塗る」といったビジュアル面の処理は、すべてCSSの領分となる。JSは状態(State)を管理し、CSSは見た目(View)を制御するという理想的な分離が実現できる。

この手法により、JSのコード量は大幅に削減される。DOMの書き換え(クラスの付け外し)を最小限に抑えられるため、ブラウザの再描画コストも低減され、結果としてパフォーマンスの向上につながるのだ。

範囲スタイリングの魔法

範囲スタイリングの魔法

さて、いよいよ本題である「範囲内のスタイリング」について解説する。クラスを一つずつ付与することなく、CSSだけで「開始日と終了日の間」を特定するには、高度なセレクタの組み合わせが必要だ。

兄弟要素セレクタ(~)との組み合わせ

範囲を指定するための第一歩は、後続兄弟結合子(~)を使うことだ。これは「ある要素より後ろにある兄弟要素」をすべて選択する記号だ。:nth-child(1 of :has(:checked)) ~ .date と記述すれば、1番目にチェックされた日付より後ろにあるすべての日付を選択できる。

否定擬似クラス(:not)による制御

しかし、これだけでは「終了日より後ろの要素」まで選択されてしまう。そこで :not セレクタを組み合わせて、範囲を制限する。具体的には、「2番目にチェックされた要素より後ろにある要素ではないもの」という条件を加えるのだ。

.isRangeSelected :nth-child(1 of :has(:checked)) ~ :not(:nth-child(2 of :has(:checked)) ~ .date) {
  background-color:rgb(228 239 253);
}

この一見複雑なコードを分解すると、「1番目のチェック要素より後にある要素」の中から、「2番目のチェック要素より後にある要素」を除外していることになる。結果として、1番目と2番目の間にある要素だけが綺麗に抽出されるという仕組みだ。

ステップ1:1番目のチェック以降をすべて選択(~)
1234567
チェック済み  ~で選択された範囲  対象外
ステップ2:2番目のチェック以降を除外(:not)→ 範囲が確定
1234567
開始日・終了日  選択範囲(2〜5の間)  対象外

※このデモはCSSの概念を視覚化したイメージだ。実際の動作はブラウザのデベロッパーツール等で確認してほしい。

実務におけるメリットと独自の分析

実務におけるメリットと独自の分析

この新しいアプローチには、単に「コードが短くなる」以上の価値がある。Web制作の実務において、どのようなインパクトをもたらすのかを考察してみよう。

コードの保守性とパフォーマンス

最大のメリットは、JavaScriptがDOMの状態を過剰に意識しなくて済むようになることだ。従来の手法では、日付がクリックされるたびに、範囲内の全要素をループで回して .is-in-range といったクラスを付け替える必要があった。要素数が多い場合、この処理は無視できない負荷になる。

一方、今回の手法では、JSが行うのは「どのチェックボックスをオンにするか」という最小限の状態変更のみだ。見た目の更新はブラウザのCSSエンジンがネイティブで高速に処理するため、ユーザー体験はより滑らかになる。また、スタイルの変更が必要になった際も、JSを触ることなくCSSの修正だけで完結する保守のしやすさがある。

アクセシビリティへの配慮

この実装は、アクセシビリティ(利用しやすさ)の観点からも優れている。ネイティブのチェックボックスをベースにしているため、スクリーンリーダーなどの支援技術に対しても「どの項目が選択されているか」という情報を標準的な方法で伝えることができる。見た目だけでなく、情報の構造としても正しい状態を保ちやすいのだ。

ただし、注意点もある。:nth-child(n of selector) は比較的新しい機能であるため、古いブラウザ(特に数年前のスマートフォンなど)では動作しない可能性がある。実務で導入する際は、対象となるユーザーのブラウザ利用状況を確認し、必要に応じて基本的な背景色のみを適用するようなフォールバック(代替処理)を用意するのが賢明だろう。

この記事のポイント

  • :nth-child(n of selector) は特定の条件に合う要素の中だけで順番を数えられる
  • JavaScriptは状態管理に専念し、複雑な範囲スタイリングはCSSに任せるのが現代流
  • 兄弟要素セレクタ(~)と否定擬似クラス(:not)を組み合わせることで範囲を特定できる
  • DOM操作の削減により、コードの保守性とパフォーマンスの両立が可能になる
海田 洋祐

・ 複数業界における17年間のデジタルビジネス開発経験 ・ ウェブサイト開発のためのHTML、PHP、CSS、Java等の実用的知識 ・ 15ヶ国語対応の多言語SaaSの開発経験 ・ 17年間にも及ぶ、Eコマース長期運営経験 ・ 幅広い業界でのSEO最適化の豊富な経験

メッセージを残す