縦書きテキストがランドスケープで列折り返しする原因と、orthogonal flow(直交フロー)でビューポート高が制約になる仕組み
LPの制作中、特定の条件でだけ発生する縦書きのレイアウト崩れに遭遇しました。
横書きの親要素内に配置したwriting-mode: vertical-rlのキャッチコピーが、スマートフォンをポートレートからランドスケープに回転したときだけ途中で列折り返しし、固定幅のボックスからはみ出すという現象です。
調査を進めると、次の2つを分けて考える必要があることが分かりました。
- CSS Writing Modesのorthogonal flow(直交フロー)におけるauto-sizing仕様
- 仕様だけでは説明できない、WebKitのレイアウト無効化漏れとみられる挙動
本記事では、仮説を数値で切り分けた調査プロセスと、仕様上の列折り返しが起こる仕組み、WebKitの実装不具合を疑った理由、white-space: nowrapによる修正までをまとめます。
現象
問題が起きたキャッチコピーは、次のような構成でした。
- 横書きの親要素内に
writing-mode: vertical-rlの縦書きテキストを配置 - キャッチコピーを1行ずつ別の要素に分け、それぞれを固定幅の丸角ボックスとして表示
スマートフォンをポートレートからランドスケープに回転すると、次のように崩れました。
- 縦書きテキストが途中で列折り返しし、固定幅のボックスからはみ出す
- 折り返し後の要素高がランドスケープ時のビューポート高よりもはるかに小さく、縦方向に潰れて見える
- ポートレートに戻しても崩れが解消せず、コンテナ幅や配置もランドスケープ時の状態を引きずる
今回の条件ではポートレート表示だけでは再現せず、実機をランドスケープへ回転して初めて問題が現れました。通常の確認がポートレート中心だと見落としやすいうえ、2と3の挙動はCSSの仕様だけでは説明できませんでした。
ライブデモ
下のデモでは、問題のLPと同じ条件を簡略化して再現しています。
100vwに連動してremが拡大する- 縦書きのキャッチコピーを1行1要素で表示する
- 各要素を丸角のボックスにする
記事内のiframeは高さを620pxに固定しています。PCではブラウザのウィンドウ幅を広げると、文字だけが大きくなるため、仕様に基づく列折り返しを確認できます。
実機、特にiOS Safariで確認する場合は、「別タブで開く」からデモを開いてデバイスを回転してください。デモでは、ビューポート高付近で起こる通常の列折り返しと、それよりもはるかに小さい位置で起こる想定外の折り返しを区別して表示し、リサイズや回転ごとの計測値を記録します。
調査プロセス
コンソールログを使い、考えられる原因を段階的に切り分けました。
仮説1: ブレイクポイントをまたいでPCレイアウトになっている?
ランドスケープへの回転でビューポート幅が広がり、メディアクエリのブレイクポイントをまたいでPC用レイアウトが適用された可能性を疑いました。
しかし、matchMedia('(min-width: 768px)')は回転後もfalseのままでした。ブレイクポイントはまたいでいないため、この仮説は棄却できます。
仮説2: JSで管理する--vwの更新が漏れている?
このLPでは、ビューポート幅に連動してremをスケールさせるため、JavaScriptで--vwカスタムプロパティを管理していました。回転時に値が更新されず、ポートレート時の値が残っている可能性も考えました。
ログを確認すると、--vwは回転後のビューポート幅に正しく追従していました。この仮説も棄却です。
計測項目を増やして列折り返しを特定する
そこで、ルートのrem実測値、対象要素のgetBoundingClientRect()、font-sizeを、リサイズ完了時に記録しました。
const logProbe = (label: string) => { const rem = Number.parseFloat( getComputedStyle(document.documentElement).fontSize ) const line = document.querySelector<HTMLElement>('.catch-line') if (!line) return
const rect = line.getBoundingClientRect()
console.log(label, { rem, fontSize: getComputedStyle(line).fontSize, lineW: rect.width, lineH: rect.height })}
計測結果は次のとおりです。
| 状態 | rem実測 | 1列の期待幅 | 要素幅(lineW) | 要素高(lineH) |
|---|---|---|---|---|
| portrait | 15.72 | 35.4 | 35.4 ✔ | 89.4(正常) |
| landscape(修正前) | 29.36 | 64.4 | 64.4 ✔ | 91.8(期待値約167pxのほぼ半分) |
| landscape(修正後) | 29.36 | 64.4 | 64.4 ✔ | 165.2 ✔ |
フォントサイズと1列分の幅は正しい一方、縦書きテキストの進行方向に当たる要素高だけが期待値のほぼ半分になっていました。
この結果から、サイズ計算全体が壊れているのではなく、テキストが2列に折り返していることを特定できました。
仕様上の列折り返しと、実装不具合とみられる挙動
今回の現象を理解するには、まず仕様上起こり得る列折り返しを確認し、そのうえで実測値に残る不自然な点を分けて考える必要があります。
1. orthogonal flowではビューポート高が制約になり得る
横書きの要素内に縦書きの要素を配置すると、親子の書字方向が直交します。CSS Writing Modesでは、この状態を orthogonal flow(直交フロー) と呼びます。
ここで注意したいのが、論理軸と物理方向の対応です。
writing-mode: vertical-rlでは、文字が上から下へ並ぶ方向、つまり物理的な「高さ」がインライン軸に当たります。そのため、縦書きテキストが縦方向にどれだけ伸びられるかは、縦書き要素の利用可能インラインサイズによって決まります。
CSS Writing Modes Level 4の§7.3 Orthogonal Flowsでは、直交フローで利用可能インラインサイズを確定できない場合に、追加の制約値を使うことが定められています。その候補には、固定された祖先のサイズやスクロールポートのサイズ、初期包含ブロックのサイズが含まれます。
今回の構成では、縦方向の長さを制約する確定値がほかになかったため、実質的にビューポート高が縦書きテキストの利用可能な長さの上限として働きました。
ランドスケープでは、次の2つが同時に起こります。
- ビューポート幅が広がり、
100vw連動のremによって文字が大きくなる - ビューポート高は低くなり、縦方向に使える長さが短くなる
つまり、テキストは長くなる一方、使える高さは短くなるため、必要な長さが制約値を超えた縦書き要素は列折り返しします。
この仕様上の挙動はライブデモでも確認できます。
ただし、実案件で計測した行は、期待される要素高が約167pxで、ランドスケープ時のビューポート高は393pxでした。本来は1列に収まるはずであり、実測値の91.8pxという位置で折り返したことは、この仕様だけでは説明できません。
2. WebKitが古いレイアウト値を再利用した可能性
実測結果からは、WebKitが回転前のレイアウト値を再利用した可能性が考えられます。
ポートレート時の要素高は89.4pxでした。回転後、remは15.72pxから29.36pxへ約1.87倍になり、テキストに必要な高さも約167pxへ増えています。
仕様に沿ってランドスケープ時の値で再計算されれば、次の関係になります。
テキストに必要な高さ 約167px < ビューポート高 393px
したがって、この行は折り返さないはずです。
しかし、修正前の実測値は91.8pxでした。これはポートレート時の89.4pxに近く、回転前に計算された要素高、またはそれに関連する制約値が残ったまま、新しいフォントサイズでテキストが再配置されたと考えると説明しやすい値です。
また、ポートレートへ戻しても崩れが解消しなかったことも、単純なビューポート寸法とフォントサイズだけでは説明しにくい挙動です。回転時の再レイアウトで必要な値が正しく無効化されず、古い値が引き継がれた可能性を疑う材料になりました。
ただし、これは実測値から組み立てた仮説であり、WebKit内部の処理を直接確認したものではありません。
WebKitには、直交配置要素の位置計算や、再レイアウト時に以前の包含ブロック値を参照してしまう問題を修正した例があります。今回と同一の不具合を示すものではありませんが、直交フローや再レイアウトにおいて古い計算値が問題になり得ることを示す参考事例です。
記事内のデモで確認できる範囲
記事内のiframeは、主に仕様に基づく列折り返しを確認するためのものです。iframeのリサイズと、iOS Safariでトップレベル文書を実際に回転する操作では条件が異なるため、WebKitの実装不具合とみられる挙動まで必ず再現するわけではありません。
2つ目の挙動を確認する場合は、デモを別タブで開き、iOS Safari実機でデバイスを回転してください。
次のいずれかが起きた場合は、仕様上の制約だけでは説明しにくい挙動です。
- ビューポート高よりもはるかに小さい位置で折り返す
- ポートレートに戻しても崩れた状態が残る
ただし、端末やOS、Safariのバージョンによっては再現しない可能性があります。
仕様上の列折り返しを確認する最小コード
次のコードでは、直交フローの仕様による列折り返しを確認できます。
<!DOCTYPE html><html lang="ja"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>orthogonal flow column wrap repro</title><style> .catch-line { writing-mode: vertical-rl; font-size: 24px; padding: 8px; border: 1px solid #333; border-radius: 8px; }</style></head><body> <p class="catch-line">縦書きのキャッチコピーがここに入ります</p></body></html>
親要素の高さは確定していないため、縦書き要素が縦方向に使える長さは、最終的に初期包含ブロックの高さによって制約されます。ウィンドウの高さをテキストに必要な長さより小さくすると、テキストが次の列へ折り返します。
このコードで確認できるのは仕様上の列折り返しであり、実案件で観測したWebKit固有とみられる挙動の完全な再現ではありません。
修正: white-space: nowrapを指定する
今回の修正は、次の1行です。
.catch-line { white-space: nowrap;}
今回のキャッチコピーは、マークアップ側ですでに1行ずつ別の要素に分けていました。そのため、ブラウザが要素内でさらに折り返す必要はありません。
white-space: nowrapを指定するとソフトラップが禁止され、テキストは1列のまま必要な高さを取ります。利用可能インラインサイズの制約値が正しくても、古い値が残っていたとしても、折り返しを起点とするレイアウト崩れを回避できます。
ただし、これは1行として表示することが確定している短い縦書き要素に適した修正です。長文やユーザー入力など、内容の長さが変わる要素へ一律に指定すると、ビューポート外へのはみ出しが起こる可能性があります。その場合は、最大サイズやオーバーフローの扱いも合わせて設計する必要があります。
まとめ
- 横書きの親要素内に置かれた縦書き要素はorthogonal flowとなる。
vertical-rlでは物理的な高さがインラインサイズに当たるため、writing-modeが親子で直交する場合は、物理的な幅・高さと論理的なインライン・ブロック軸を分けて考える - 直交フローで利用可能インラインサイズを確定できない場合は追加の制約値が使われ、今回の構成ではビューポート高がその制約として働いた
- ランドスケープでは、
vw連動で文字が大きくなる一方、ビューポート高が低くなる。この2つが同時に起こる条件では、仕様上の列折り返しが発生しやすい font-size、要素幅、要素高を数値で比較すると、サイズ計算の異常なのか折り返しなのかを切り分けやすい。今回も、要素高だけが期待値のほぼ半分という結果が特定の決め手になった- 実案件で観測した91.8pxでの折り返しと、回転を戻しても直らない挙動は、WebKitが回転前のレイアウト値を再利用した可能性を示している。ただし、これは実測に基づく推定であり、仕様で説明できる挙動と実装固有とみられる異常は分けて扱う
- 1行1要素として設計した縦書きキャッチコピーでは、
white-space: nowrapによって列折り返しを防げる
参考リンク
- CSS Writing Modes Level 4 — §7.3 Orthogonal Flows — 直交フローのauto-sizing仕様
- MDN —
writing-mode - MDN —
white-space - MDN — CSS論理的プロパティと値 — ブロックサイズとインラインサイズの考え方
- Changeset 277391 – WebKit —
vertical-rlの直交配置要素で、borderとpaddingを考慮した位置計算へ修正した例 - Changeset 267503 – WebKit — 再レイアウト時に以前の包含ブロック値を参照してしまう問題を修正した例
- Debugging vertical layouts in 2021 | Chen Hui Jing — 縦書きレイアウトをブラウザ間で検証した記録