本文へスキップ
Articles

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ページ」に限定されます。

整理したい気持ちは、たいてい正しい直感です。ただ「気持ち悪いから直す」と「壊れているから直す」は別の作業で、後者を先にやったほうが、限られた時間の使い道としては合っていました。

持ち帰れること

今回いちばん得をしたのは、やらないと決められたことでした。整理に半日使う代わりに、計測に30分使って1行直して終わりました。差の半日は記事を書く側に回せています。