2026-08-20 公開 / Webツール
「レスポンシブが崩れている気がする」を、感覚ではなく1つの数字で測る
サイト全体のCSSを見たら、画面幅の切り替え位置が19種類に散らばっていました。480、520、540、600、640、680、700、720、734…。設計ではなく、ページを作るたびに継ぎ足した結果です。整理しようと思いましたが、その前に実際に壊れているページがいくつあるかを測りました。答えは1ページでした。
「ブレークポイントの数」は、利用者から見える問題ではない
切り替え位置がばらばらなのは、たしかに気持ちの悪い状態です。ただ、これを3種類に揃えたとして、利用者の画面で何が変わるのかを考えると答えが出ませんでした。誰も切り替え位置の値を見ていません。見ているのは「文字が切れているか」「横に余計なスクロールが出るか」です。
しかも揃える作業には副作用があります。たとえば700pxで1カラムに切り替えていたページを640pxに寄せると、640〜700pxの範囲では今まで1カラムだったものが2カラムのままになります。そこで要素が詰まって崩れるかどうかは、実際に見るまで分かりません。
つまりこの整理は、利用者に見える利益がゼロで、利用者に見える損害の可能性だけがある作業でした。やる理由をうまく言えないなら、先に測るべきだと考えました。
測る指標は1つで足りる
レスポンシブの崩れは色々な形で出ますが、機械的に、誤判定なく検出できるものが1つあります。横スクロールの発生です。
document.documentElement.scrollWidth > document.documentElement.clientWidth
これが成り立つとき、ページの中身が画面の幅に収まっていません。スマートフォンで左右に振れる、あの状態です。意図してこうする理由はまず無いので、成り立ったら不具合だと断定できます。「なんとなく窮屈」のような主観が入らないのが利点です。
逆に言えば、これで検出できないもの(文字が小さすぎる、余白が詰まりすぎ、ボタンが押しにくい)は別途目で見るしかありません。ただ、まず自動で潰せるところを潰してから目視に入るほうが、目視の対象がぐっと減ります。
実際のコード
ヘッドレスブラウザで各ページを開き、画面幅を変えながら上の式を評価します。ポイントはページを読み込み直さないことです。幅ごとに読み込むと6倍時間がかかりますが、読み込みは1回にして画面サイズだけ変えれば、再描画だけで済みます。
const { chromium } = require('playwright');
const WIDTHS = [320, 375, 414, 768, 1024, 1280];
const browser = await chromium.launch();
const page = await (await browser.newContext()).newPage();
await page.goto(url, { waitUntil: 'domcontentloaded' });
for (const w of WIDTHS) {
await page.setViewportSize({ width: w, height: 800 });
await page.waitForTimeout(180); // 再描画を待つ
const r = await page.evaluate((w) => {
const de = document.documentElement;
const over = de.scrollWidth - de.clientWidth;
if (over <= 1) return null; // 1px は誤差として許容
const bad = [];
for (const el of document.querySelectorAll('body *')) {
const b = el.getBoundingClientRect();
if (b.width === 0 && b.height === 0) continue;
if (getComputedStyle(el).position === 'fixed') continue;
if (b.right > de.clientWidth + 1) {
bad.push(`<${el.tagName.toLowerCase()} class="${el.className}"> 幅${Math.round(b.width)}`);
}
}
return { over, bad: bad.slice(0, 3) };
}, w);
if (r) console.log(` 幅${w}px: ${r.over}px はみ出し`, r.bad);
}
はみ出している要素まで拾っておくのが大事です。「このページが壊れている」だけだと結局手で探すことになりますが、要素名まで出れば直す場所がその場で決まります。
幅の選び方は、320を必ず入れています。今どき320pxの端末は少ないのですが、いちばん厳しい条件で通れば他は通るので、検出器としては都合が良いからです。
最初に書いたとき、はみ出し要素の判定に b.left < -1(左側にはみ出している)も入れていました。これは誤判定のもとでした。画面外に飛ばしておく実装(left: -9999px など)は普通に使われていて、左方向は横スクロールを発生させません。判定は右端だけで足ります。
結果: 87ページ中1ページ
6つの幅で87ページ、のべ522回の判定で、横スクロールが出たのは1ページだけでした。19種類のブレークポイントは、見た目に反して実害をほとんど出していなかったことになります。
原因もはっきり出ました。
幅320px: 94px はみ出し
<code> 幅48 ← index.html.bak-20260721
記事本文に埋め込んだ23文字のファイル名でした。日本語は文字単位で折り返るので、日本語だらけの本文はどんなに狭くても収まります。ところが連続したASCIIは単語の途中で切れないので、そこだけ折り返らずに突き抜けます。ファイル名、パス、長いURL、ハッシュ値。技術記事に必ず出てくるものばかりです。
直し方は1行、ただし置き場所を選ぶ
該当ページのCSSに追記すれば直りますが、それはやりませんでした。同じ不具合は次に書く記事でも必ず起きるからです。個別に直すと、記事を書くたびに同じ地雷を踏み直すことになります。
code, kbd, samp { overflow-wrap: anywhere; }
これを共通CSSのほうに入れました。overflow-wrap: anywhere は「どこでも折り返してよい」という指定です。似たものに break-all がありますが、そちらは日本語まで不自然な位置で切りにいくので、ASCIIだけを救いたいこの用途では anywhere が合います。
直したあと同じ計測をもう一度回して、87ページ×6幅で0件になったことを確認しました。直したつもりで直っていないのが一番まずいので、同じ物差しで測り直すところまでを1セットにしています。
それで、19種類のブレークポイントはどうしたか
そのままにしました。実害が1件も無いことが分かった以上、揃える作業は利用者から見て何の変化も生まない一方で、中間の幅で崩す危険だけを持ち込みます。
代わりに、共通CSSのコメントとして「これから作るページはこの3つだけを使う」という基準を書き残しました。既存を一斉に直すのではなく、そのページを別の用件で触ったときに、ついでに寄せる。そうすれば、崩れたときの原因が「今日触った1ページ」に限定されます。
整理したい気持ちは、たいてい正しい直感です。ただ「気持ち悪いから直す」と「壊れているから直す」は別の作業で、後者を先にやったほうが、限られた時間の使い道としては合っていました。
持ち帰れること
- レスポンシブの健全性は
scrollWidth > clientWidth1つで機械的に測れる。主観が入らないので、直す前と後で同じ物差しが使えます - ページの読み込みは1回、画面サイズだけ変える。幅ごとに読み込むと6倍かかります
- はみ出した要素名まで出力する。ページ名だけだと結局手で探すことになります
- 320pxを必ず入れる。いちばん厳しい条件で通れば他は通るので、検出器として効率が良いです
- 日本語の本文でも、ASCIIの連続だけは折り返らない。
codeにoverflow-wrap: anywhereを、個別ページではなく共通CSSに置きます - 「散らかっているから直す」の前に、実害を数える。数えた結果やらないと決められるなら、それも成果です
今回いちばん得をしたのは、やらないと決められたことでした。整理に半日使う代わりに、計測に30分使って1行直して終わりました。差の半日は記事を書く側に回せています。