📖

[CSS組版]腑分けして理解したいCSS組版 ページ分割制御

2024/12/21に公開

CSS組版アドベントカレンダー2024の21日目の記事です。

https://adventar.org/calendars/10448

本記事では簡単のため、特別に言及しない限り、段分割・ページ分割を総称してページ分割と呼びます。改丁・改頁みたいな区別も語としてはしません。

導入

一般のWebページでは、コンテンツの途中で内容が次のページにいって分割されて困るということはあまりありません(トピック的な区切り以外で、ですが。有料コンテンツ誘導とかは知らん)。ビューポートから隠れていて困るということはありますが、スクロールすればそのまま続きを読み始められます。

ページメディアは左端右端に加え、(通常)一定間隔で上端下端があります。多分これ前も書いたな。冊子本では、「隣のページに目を移す」または「ページを捲り目も移す」というユーザのコストが発生します。(代わりにページ数による移動コストは1ページでも数百ページでもそう変わりません。)
行分割も「行が長すぎる」「行分割の頻度が多すぎる」といった状況では無視できないコストがありますが、一般には手の動作や目の移動距離という点でページ分割の方が高いコストを強制します。このコストは、読書に於ける集中やコンテンツの繋がりを弱める「切断力」として考えることができます。切断はネガティブな意味のみではなく、「章の区切りでページが切り換わる」といったポジティブな事象もあります。そして「見開きの前側ページ(verso)から後側ページ(recto);隣のページに目を移す」と「rectoからverso;ページを捲り目も移す」ではコストが違います。

この話はまだまとまっていないので、今日はページの分割制御関連プロパティです。(こういう話に興味があれば,『ページと力 手わざ、そしてデジタルデザイン[増補新版]』(鈴木一誌,青土社,2018)をおすすめします。)

分割と分割抑制の温度差

例えばhr要素にページ区切りのスタイルをあてて、「改ページができたー!」とするのは比較的簡単で、その場の話で済みます。これは、後ろのページはこれから用意するからです。

一方、分割の抑制は「ページ残り0.5行分なのでここで改ページします」というときに「いや、そこを何とか」と抑えるわけで、そのままでは詰め込みすぎでページマージンなどにコンテンツがはみ出し(オーバーフロー)ます。それもそれで困るので、「じゃあ君達まとめて次のページね」というように分割位置の再調整を行うことになります。どうにもならない絡まり方の指定のときは「動かせないんで」とオーバーフロー状態で組版してしまうのは処理系の動作としては正常です。

ページ泣き別れのバリエーション

「ページ分割でコンテンツが前ページと後ページに分かれる」という書き方だとごく単純な事柄に見えますが、もう少し詳解します。

  • コンテンツ途中での分割(抑制)
  • コンテンツの前(後)での分割(抑制)

前者は段落内やリスト項目の途中といった「同じ構造のコンテンツでの分割」、後者は見出しと本文、図版とキャプションのように「異なる構造のコンテンツでの分割」でよく登場します。

コンテンツ途中での分割

コンテンツ途中での分割も、更に内実、というか必要なプロパティが別れます。

  • どこからどこまで(の行数)の分割なら行う widows orphans
  • コンテンツ内部全体で分割抑制する break-inside

孤立行

本によって名称が異なったりしますが、(大抵は段落内での)ページ端での最低行数の指定です。

https://drafts.csswg.org/css-break/#widows-orphans

  • orphans ページ最後の段落の、最初の数行
  • widows 段落最後の数行がページ最初

日本語の小説など、あまり構造が入り組まず同じサイズの文字で版面を埋めるようなレイアウトの文書では、これらのプロパティにそれぞれ1を指定するところからが始まりです。初期値の挙動、2とかであると、「版面の端まで行っていないのに改ページされた」ということが起こります。機序としては次ですね。

  1. この行だけnページ目に残すわけにはいかない(widows, orphansが>1)
  2. 段落をまとめてn+1ページ目に移動しよう
  3. nページ目のページ端までいっていないのに改ページされている!

「widows=2, orphans=2 で2行の段落コンテンツがページ端に来たらどうなるのか?」これは「孤立行の行数は0」なので普通に前ページに配置されるでしょう。

コンテンツ内部全体で分割抑制する break-inside

後述のbreak-beforebreak-afterは強制分割または分割抑制の両方を値として持ちますが、break-insideautoでとくに何もしない(分割可状態)ため、使われるときは分割抑制のためです。(その解除に使われることはあるかもしれません。)

:where(h1,h2,h3,h4,h5,h6) {/*見出し内で改段、改ページさせない*/
  break-inside: avoid-column;
}

値としてはautoavoid-regionavoid-columnavoid-pageとあります。

見出しやキャプションのように、同一ページ内で収めることが当たり前の箇所で使うのがメインです。コードブロックのように行数が読めないコンテンツでは少し使うのが躊躇われます。

https://drafts.csswg.org/css-break/#break-within

コンテンツ前後での分割制御 break-before break-after

はい。break-beforebreak-afterは強制分割または分割抑制の両方を値として持ちます。

抑制

先ずは抑制の使い方です。抑制の場合、値はbreak-inside同様です。

<figure>
  <img src="figA.png" alt="" />
  <figcaption id="figA">A</figcaption>
</figure>
figure:has(img) > figcaption {
    break-before: avoid-column;
}

画像を含むfigureは、画像が途中で分割されることはないので例示としてはよくないんですが。

/* 次でも実質的に大体同じことになる
figure:has(img) {
  break-inside:avoid;
}
*/

見出しと本文でもやってみましょう。

<section><h2 id="sec1">タイトル</h2>
  <p>本文です。</p>
  <p>...</p>
....
</section>

sectionの中身が何行、何ページ分になるか分からない以上、sectionに対しbreak-inside:avoidが使えない箇所です。

section > h2 + p {
    break-before: avoid-page;/*同じページ内なら段の前後でも許容するが別ページはNG*/
} 

h2とpが分かれることは防ぎますが、pの内容途中での分割はそのままです。そして、h2とpの組み合わせでないなら(例えば見出し直後にfigureとか)適用されません。

強制ページ分割

rectoversoleftrightpagecolumnalwaysallが値です。

:::
alwaysallがややこしいのですが、ページの途中で段組のコラム欄を作っているようなときに挙動に違いが出ます。

  • コラム欄内でalwaysを指定した場合、行われるのは改段です。
  • コラム欄内でallを指定した場合、改段もする、改ページもする、という挙動になる、はず。
    :::

組版的には、「コンテンツを常に偶(奇)数ページ始まりにする」といったレイアウトを行うときに有用なのがrectoversoleftrightの指定です。

「セクションは常に偶数ページから始まる」としてみましょう。

<section><h2 id="sec1">タイトル</h2>
  <p>本文です。</p>
  <p>...</p>
....
</section>
section:has(>h2) {
  break-before: verso;
}

注意点として、指定内容と位置によっては、偶数ページの途中で次の偶数ページを探して2ページ分改ページをしてしまうような事象も有り得ます。

組版・ドキュメンテーション勉強会

Discussion