スマートフォン・タブレットからインターネットサーバーオペレーション

APPW.jp
 
CSS NOTES · INDEX

症状から引く、CSS の索引

overflow が効かない。sticky が動かない。 スクロール中だけ右へずれる。 自分のサイトで実際に踏んだ25の症状を、原因と対処で引けるようにまとめました。 確定版のテンプレートと、はみ出し要素を特定する診断スクリプトも置いています。

[ 索引 ]

症状で探す

困っている症状を入力するか、分類で絞り込んでください。 詳しい解説は各記事にあります。

[ テンプレート ]

確定版のベースCSS

3本の記事で得た対処を、そのまま使える形にまとめたものです。 自分のサイトでは、Chrome(Android)と Safari(iPadOS)の実機で確認しています。

base.css
html {
  overflow-x: hidden;              /* 想定外のはみ出しを隠す保険 */
  overscroll-behavior-x: none;   /* iOS の左右の揺れを止める */
}

body {
  will-change: transform;        /* 合成レイヤーへ昇格 */
  position: relative;            /* ::before の基準にする */
  overflow-x: clip;                /* sticky を壊さずクリップ */
  z-index: 0;                     /* 重ね合わせ文脈をつくる */
  -webkit-text-size-adjust: 100%; /* iOS の文字拡大を防ぐ */
}

/* 背景の模様は擬似要素へ逃がす */
body::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image: ...;
  background-size: 44px 44px;
}

要素側の原則

スクロール容器
/* 横スクロールさせたい箱 */
.scroll-box {
  overflow-x: auto;
  max-width: 100%;
  min-width: 0;                    /* 縮む余地 */
  -webkit-overflow-scrolling: touch;
}

/* 中身がブロック要素で、折り返さない場合だけ */
.scroll-box > .line {
  width: max-content;
  min-width: 100%;
  white-space: pre;
}

/* 縦だけスクロールさせたい箱 */
.list-box {
  max-height: 230px;
  overflow-y: auto;
  overflow-x: clip;              /* 横も必ず明示する */
  min-width: 0;
}

/* フレックスの子で省略表示したい */
.flex-parent > .ellipsis {
  min-width: 0;                    /* これが無いと効かない */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
そのまま使う前に。 will-change常時メモリを確保しますoverflow-x: clip未対応の環境では無視されますhtmloverflow-x: hidden は、 原因を消したうえでの保険として置いています。 どれも副作用があるので、症状が出ていないなら入れる必要はありません。
[ 診断 ]

はみ出し要素を特定する

横スクロールが発生しているとき、どの要素が原因かを調べるスクリプトです。 開発者ツールのコンソールに貼り付けて実行します。

実行の前に。 htmlbodyoverflow-x: hidden一時的に無効にしてください。隠れていると検出できません。
find-overflow.js
(function(){
  var de = document.documentElement, vw = de.clientWidth;
  console.log('画面幅: ' + vw + '  文書幅: ' + de.scrollWidth);

  // 祖先のどこかで横がクリップされていれば、ページ幅には響かない
  function contained(el){
    for (var p = el.parentElement; p && p !== de; p = p.parentElement)
      if (getComputedStyle(p).overflowX !== 'visible') return true;
    return false;
  }

  var hits = [];
  document.querySelectorAll('body *').forEach(function(el){
    var r = el.getBoundingClientRect();
    if (r.width === 0 && r.height === 0) return;
    if (getComputedStyle(el).position === 'fixed') return;
    if (r.right <= vw + 1 && r.left >= -1) return;
    if (contained(el)) return;
    hits.push({ el: el, tag: el.tagName.toLowerCase(),
      cls: el.className && typeof el.className === 'string'
           ? '.' + el.className.trim().split(/\s+/).join('.') : '',
      over: Math.round(r.right - vw) });
  });

  // 祖先を除き、末端だけ残す
  var leaves = hits.filter(function(h){
    return !hits.some(function(o){
      return o.el !== h.el && h.el.contains(o.el); });
  });

  console.log('はみ出し: ' + leaves.length + '件');
  console.table(leaves.map(function(h){
    return { タグ:h.tag, クラス:h.cls, 超過:h.over }; }));
  leaves.slice(0,3).forEach(function(h,i){
    h.el.style.outline = '3px solid ' + ['red','orange','yellow'][i];
  });
})();
0件と出たら。 寸法を持たないものが原因です。 背景(background-image)、影、transform、アウトラインなど。 background-image: none を一時的に入れて症状が変わるか確かめるのが早道です。 ③の記事で扱っています。
スクロールしてから実行する。 画面外の要素は、まだレイアウトが確定していないことがあります。 症状が出る位置までスクロールしてから実行すると、捕まることがあります。
[ 広告枠 ]

埋まらなかった広告枠

3本の記事を書き終えたあとで、本当の原因が見つかりました。 スクロール中に右へずれる症状は、CSS ではなく 配信されなかった広告枠が起こしていました。

規則性で確定した

決め手は、リロードを繰り返すうちに気づいた規則でした。

広告の状態症状
3カ所すべて表示された出ない
2カ所だけ表示(1カ所が空白)出る
3カ所すべて削除した出ない
「広告が表示されること」ではなく「枠が埋まらないこと」が引き金でした。 配信されなかった枠は data-ad-status="unfilled" が付き、 中身が空のまま幅を持って残ります。 表示されないので目視では気づけません。

対処

未配信の枠を畳む
/* 配信されなかった広告枠を消す */
ins.adsbygoogle[data-ad-status="unfilled"] {
  display: none !important;
}

/* 高さが確定するまでの1秒ほどを予約しておく */
aside.ad-wrapper {
  width: 100%;
  max-width: 100%;
  min-height: 200px;   /* 実際の広告高さに合わせる */
  overflow: hidden;
  contain: layout;
}
contain の値に注意。 有効なのは none / strict / content / size / inline-size / layout / style / paint です。 無効な値が混ざると、宣言全体が無視されます。 なお高さの予約は min-height が本体なので、 contain が無くても目的は果たせます。

開発環境で再現しなかった理由

この症状を長く追えなかったのは、確認手段が2つとも「広告のないページ」だったからです。

環境広告症状
ローカルのファイル配信されない出ない
localhost のプレビュー配信されない出ない
本番サーバー配信される出る
この非対称は、道具として使えます。 localhost で出ず、本番だけで出るなら、 広告など本番にしか存在しない要素を疑う。 両方で出るなら CSS や HTML の問題。 1回の比較で、原因の層を切り分けられます。

直したのに、直らなかった理由

症状はひとつに見えましたが、原因は3層に重なっていました

内容結果
① CSS の不備min-width:0 欠如、overflow-y 単独指定実在した。直して正解
② 初回の計算読み込み直後のビューポート幅が不完全再計算で回避
③ 空の広告枠未配信の枠が幅を持って残る真因

①を直したとき「解消した」と見えたのは、おそらく 広告の配信状況がたまたま変わったからです。だから再発した。 複数の原因が重なると、部分的な対処が効いたり効かなかったりします。 「直った」の判定そのものが難しくなる。

真因が分かってから、対処を外す

③を潰したあとで、それまでの対処を再検証しました。

対処判定
未配信の枠を畳む必須
背景を ::before へ逃がす必要
要素の min-width:0 / max-width:100%必要
.ad-wrapper で高さを予約必要
html { overflow-x: hidden }保険として残す
will-change: transform不要
load 後の reflow スクリプト不要
対処を足すのは簡単ですが、外すのは難しい。 効いているように見えるものを消すには、真因が分かっている必要がありますwill-change は常時メモリを確保し、 reflow スクリプトは毎回走ります。 症状が消えたなら、対処も消すべきでした
[ 手順 ]

切り分けの順序

3本を書き終えて、この順で調べればよかったと思う手順です。 実際には推測から入って、何度も遠回りしました。

おすすめの順序

  • 1. 症状を確定させる — どの画面幅で、いつ(読み込み直後か、スクロール中か)出るか。
  • 2. 診断スクリプトを実行overflow-x:hidden を外した状態で。
  • 3. 出た要素を直す — たいてい min-width:0max-width:100%
  • 4. 0件なら、消して試す — 背景・影・transform を順に無効化する。
  • 5. 効果はキャッシュを消して判定 — 「無意味な記述で直った」は、更新されただけかもしれない。
  • 6. 複数環境で確認 — Chrome と Safari で有効な対処が違うことがある。
  • 7. 本番だけで出るなら、本番だけにあるものを疑う — 広告、計測タグ、遅延読み込み。
  • 8. 真因が分かったら、途中の対処を外す — 効いて見えただけのものが混ざっている。
いちばんの教訓。 CSS を読んで組み立てた推測は、3回続けて外れました。 決め手になったのは、どちらも実際に消して試した結果です。 理屈で追う時間があるなら、1行コメントアウトして読み込み直したほうが速い。

『CSS NOTES — 症状から引く索引』を公開しました。