コラム幅を文字サイズの整数倍にする

日本語の組版ではコラム幅を文字サイズの整数倍にするのが基本だ。そうすれば全角等幅の活字を字間なく並べたとき、コラム幅と行長がぴったり合い、行末の位置が揃うことになる。ただしプロポーショナル文字(ラテン・アルファベットやアラビア数字など)や行頭行末の禁則の影響で行長にばらつきが出るので、その場合は調整が必要になる。

このコラム幅についての原則はウェブでも同様だと僕は考えている。ブラウザーではInDesignとは違って行ごとの緻密な調整はできないが、それでもやはり、コラム幅はなるべく文字サイズの整数倍にするのがよいと思う。そうしておけば、行長のばらつきが起こったとしても、その変化は「短くなる」方向でしか発生しない。コラムの右端を見たとき(横組みの場合)、おおむね縦に揃っているがときおりくぼんだ箇所が発生する、という格好になるわけだ。一方コラム幅が文字サイズの整数倍になっていないと、行長が意図した幅より長くも短くもなるので、行末が不揃いになりやすい。

CSSでコラム幅を文字サイズの整数倍にするには、任意のブロックのwidthプロパティにem単位の整数値を指定すればよい。1emはすなわち全角幅なので、指定した数値がそのまま1行の文字数になる。ただし幅を固定してしまうとそれより狭い画面に収まらないので、min()関数とvw単位などを使って最大幅を制限することも必要。以下の例では、広い画面ではmainの幅が全角36文字分、狭い画面ではビューポートの90%になる。

main {
  width: min(36em, 90vw);
}

ただこのやり方では、mainの幅が40em未満の場合(36em ÷ 0.9)、コラム幅は文字サイズの整数倍ではなく半端な幅になってしまう。たとえばビューポートの幅が550pxで、文字サイズが16pxだったとする。mainの幅は495pxになり(550 × 0.9)、1行には30文字が収まるが、行末に15pxの余りが生じることになる(16 × 30 + 15 = 495)。この15pxの半端なスペースは、前述のとおり、行長のがたつきを招いてしまう。

こうした事態を避け、いかなるビューポート幅であってもコラム幅を意図どおりにコントロールする方法としては、単純にメディアクエリのブレイクポイントを複数用意するというのが考えられる。

@media (min-width: 20em) {
  :root { --measure: 18em; }
}

@media (min-width: 22.5em) {
  :root { --measure: 20em; }
}

@media (min-width: 25em) {
  :root { --measure: 22em; }
}

...

@media (min-width: 45em) {
  :root { --measure: 36em; }
}

main {
  width: min(var(--measure, auto), 90vw);
}

ビューポート幅が20em以上ならコラム幅は18文字分、22.5emあれば20文字……というふうに、任意の間隔で複数のブレイクポイントを設定し、それぞれに対してコラム幅を定義していく。この方法ならビューポートに応じた緻密なコントロールが可能だし、じっさい意図通りに動作する。このサイトでも以前はこのようにしていた。しかしこれはきりがないというか、設計するのも維持するのもたいへんなうえ、特定のデヴァイスを想定したデザインになりがちという欠点もある。

そのかわりに、CSSグリッドのrepeat()関数とauto-fit値を使うと、コラム幅をビューポートに対してリキッドにし、かつ文字サイズの整数倍に制限できる。メディアクエリは不要。以下の例では、mainの子要素の幅が90vwもしくは36em以内でつねに文字サイズの整数倍になる。

main {
  display: grid;
  grid-template-columns: repeat(auto-fit, 1em);
  justify-content: center;
  margin-inline: auto;
  width: min(36em, 90vw);
}

main > * {
  grid-column: 1 / -1;
}

まず親要素のグリッドで1em単位のコラムをスペースが許す限り生成し(grid-template-columns: repeat(auto-fit, 1em))、その子要素をグリッドの両端に合わせて配置する(grid-column: 1 / -1)。そうすることで子要素の幅を1em単位、つまり文字サイズの整数倍に制限している。justify-contentとインライン方向のマージンは、ボックス全体を中央寄せにするためのものなので、左寄せにするなら不要。repeat()関数のauto-fitauto-fillに変えても結果が変わらないように思うんだけど、この場合にはどっちを使うのが適切か確信が持てなかったので、ひとまず短く書けるauto-fitを採用しているauto-fitauto-fillのどちらが適切かについて、記事の公開後に@_yuheiyの指摘を受けた。今回の使い方ではどちらでも結果は変わらないが、minmax()関数を使ったときの挙動を考えるとauto-fillの方がコードの意図が伝わりやすいのでは、とのこと。たしかに、ここでのグリッドの使い方は典型的なものではないこともあり、より結果がイメージしやすい方を選ぶというのは理にかなっていると思う。コード例ではauto-fitを使ってますが、やっぱりauto-fillを使った方がよさそうです)

この方法がとくに効果を発揮するのは、やはりスマホなど狭い画面においてだろう。左右に最低限のマージンを確保しつつ、コラム幅は文字サイズの整数倍でとりうる最大値にするということが、実際のデヴァイスのサイズを考慮せずとも可能になる。