ビューポート幅と列数⁠の⁠対⁠応

サムネイルで画像を並べるような場合、ネイティブ・アプリケーションでは、ウィンドウ幅が変わると列数を増やすだろう。最近は、それと同時にサムネイルの大きさも調整し、常にウィンドウ幅いっぱいに並ぶように調節してくれるものが増えてきた。iTunesのアルバム・ビューがその例として挙げられる。ウェブでこれを実現するにはFlexboxでサムネイルのサイズを柔軟に決定させつつ、ビューポート幅に応じて列数を増やしていく。両者の対応にはカスタム・プロパティーを使うと楽しい。

狭い画面では3列、ちょっと広い画面では4列、もっと広い画面では5列と、段階的に列数を増やす仕組みを単純化して例に挙げる。実際にどのような感じかは、このウェブサイトのホームの一番下で確認できるだろう。こちらは折り返さず隠すものだが、基本は同じだ。

.container {
  --num-column: 3;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
}

.thumbnail {
  width: calc((100% - 1rem * (var(--num-column) - 1)) / var(--num-column));
}

@media (min-width: 720px) {
  .container {
    --num-column: 4;
  }
}

@media (min-width: 1280px) {
  .container {
    --num-column: 5;
  }
}

親の.containerでは、子の.thumbnailを折り返しつつ並べるため、列数を定義しつつFlexboxをうまく使う。後にcalc()で使うので、列数は単位なしの整数で定義する。サムネイルの余白は子で制御しても良いが、space-betweenを使ってFlexboxに任せると必ずキレイに収まるので、考慮することが減る。(多分CSSプリプロセッサーで作られた)小数の扱いがブラウザーによって違うことに由来する「1pxはみだした!」や「1px足りない……」という事態がまず起きないからだ。

子の.thumbnailでは列数の定義を利用して幅を計算する。100%から各列の間の余白(ここでは1remとした)を引き、残りを列数で割る。各列の間の数は常に列数から1を引いたものなので、これも計算できる。動的な値から計算するこの手のcalc()はCSSプリプロセッサーでは実現できない。

メディア・クエリーでは列数を上書きしていく。それだけで、余白の割り当てや幅の計算をブラウザーが調節してくれる。この場合は、今まで通りに書いてもwidthプロパティーで上書きするだけと、あまり手間が変わらない。そのため中途半端に抽象化しただけに過ぎないが、--num-columnを複数の要素とプロパティーで利用していると、それぞれに伝播させられるので効率が良くなる。


本当は列数もビューポート幅から計算できると良い。理論的には、サムネイルの最大サイズを決め、それでビューポート幅を割れば計算できる。しかし今のcalc()では単位ありの数値から単位なしの整数を作れないので、実装に落とし込むのは難しそうだ。別の方向からのアプローチがないか考えているが、まだうまく思いつかない。

また、calc()では単位を付けることは簡単だが、その逆はできないことも覚えておきたい。100rem1remで割っても100にはならないのだ。つまり、calc()でカスタム・プロパティーを使う場合は、できるだけ単位なしの数字で係数や定数として定義しておくべきだろう。


まだカスタム・プロパティーどころかcalc()すらも怪しい時勢なので使いどころは限られる。しかし、これまでのCSSプリプロセッサーの概念とは大きく違った使い方なので、意識改革をしつつ使えるところ(絶対に動く環境)では積極的に使っていきたい。