text-pretty を日本語の本文に付けると、Safari だけ右が余る
iPhone でこのブログを開いたら、本文の右側に余白が帯のように残っていた。デスクトップの Chrome では出ない。原因は Tailwind の text-pretty、つまり text-wrap: pretty だった。日本語の本文に付けると、Safari と Chrome で結果が変わる。
「余白が多い」を数える
見た目の不満はそのままだと議論になる。「少し広い気がする」で止まると、直したあとに直ったのかも分からない。先に数値にした。
測ったのは、各段落の右端と、その段落の中で最も右まで届いた行の差。日本語の本文はどの行もカラムの右端近くまで届くのが普通なので、この差が大きいほど「右が余っている」ことになる。
// 段落の右端と、その段落で最も右まで届いた行の差 = 右に残った余白。
// Range#getClientRects は行ごとの矩形を返すので、行末の位置がそのまま取れる。
const range = document.createRange();
const gaps = [];
for (const el of document.querySelectorAll("article p")) {
const box = el.getBoundingClientRect();
let maxRight = -Infinity;
for (const node of el.childNodes) {
range.selectNodeContents(node);
for (const line of range.getClientRects()) {
if (line.width) maxRight = Math.max(maxRight, line.right);
}
}
gaps.push(box.right - maxRight);
}これを Playwright で iPhone 13 相当の幅(390px、本文カラムは 350px)に流し込む。Playwright は Chromium と WebKit の両方を同じ API で動かせるので、エンジンを差し替えるだけで比較できる。
同じ CSS が、エンジンで別の結果になる
text-wrap: pretty を注入する/しないの2通りを、両エンジンで測った。
# iPhone 13 幅 390px / 本文カラム 350px
# 段落 24 個の右余白(平均 / 最大, px)
なし あり
Chromium 6.7 / 14 7.6 / 24
WebKit 6.3 / 15 38.4 / 78WebKit だけが動いている。平均で 6 倍、最大 78px。本文カラムが 350px なので、幅の 2 割以上が空いている段落があることになる。記事一覧のカード見出しではさらに大きく、最大 86px まで開いていた。
日本語は、単語の切れ目に関係なくほとんどの文字の間で改行できる。pretty は段落全体を見てより良い改行位置を選ぶプロパティなので、候補が桁違いに多い日本語では、全行を少しずつ手前で折る解が選ばれる。Chromium では平均 0.9px しか動かなかった。仕様は「より良い改行を選ぶ」としか定めていないため、どちらの結果も間違いではない。
Chrome のデバイスモードでは原理的に出ない
この崩れは公開時から入っていた。SP 表示の確認を、デスクトップ Chrome のデバイスモードでやっていたからだ。デバイスモードが変えるのは画面サイズと UA とタッチ判定で、レンダリングは Blink のまま動く。エンジン差のバグは、どれだけ丁寧に見ても出てこない。
画面幅を変える確認と、別のエンジンで見る確認は別物だ。前者をいくら重ねても後者の代わりにはならない。
実機を毎回持ち出さなくても、Playwright の WebKit は Linux 上でも動く。今回の再現・修正確認はすべてそれで完結した。
分岐させずに消す
直し方は text-pretty を外すだけで、blog 配下の 4 ファイル 10 箇所だった。修正後にもう一度測ると、WebKit の平均は 38.4px から 6.3px に戻り、Chromium と同じ値になった。
検討して捨てた案が1つある。@supports や UA 判定で Safari のときだけ pretty を切り、Chrome には残す、というものだ。捨てたのは上の表のためで、Chromium 側で pretty があってもなくても差は平均 0.9px しかない。日本語では得られるものがほぼないのに、ブラウザで分岐するタイポグラフィ規則をコードベースに抱えることになる。割に合わない。
ただし消すだけだと、次の記事を書くときに同じクラスがまた付く。定数の真上に理由を残した。
// `text-pretty` は付けない。Safari の text-wrap: pretty は段落全体を最適化するため、 // どこでも改行できる日本語だと全行が短くなり、右に大きな余白が残る(iPhone 実機で顕著)。 // Chromium は最終行しか触らないので、この崩れはデスクトップの Chrome では再現しない。 const bodyTextClass = "text-base leading-[2.15] text-[#3a3a42]";
まとめ
text-wrap: pretty は、英文で最終行に単語が1つだけ残るのを防ぐ、という文脈で語られてきたプロパティだ。単語の切れ目でしか折れない言語を念頭に置いた話で、どこでも折れる言語では前提が変わる。見た目に効くプロパティは、どの言語を想定した議論から来たものかまで見ないと、こういう食い違いが残る。
もう1つ。「なんとなく余白が多い」は水掛け論になるが、「78px」は議論にならない。見た目の不満でも、数えられる形に落とせば1回で決着がつく。