紡Book の勉強ノートに戻る
技術ブログ2026.07.16

重なりのバグは「数字を上げる」前に測る — elementFromPoint で z-index を実測する

CSSz-indexデバッグ紡Book

星座風テンプレートの表示バグを AI に直してもらったとき、その直し方の手順そのものが勉強になったので記録しておきます。修正済み。 このバグは「テンプレート29種の全景スクショを自動で撮る — ウィザード自動操縦と、途中で死んだ別ポート作戦」で撮った29種の全景を同じ条件で並べて比較したときに浮いたものです。

何が起きていたか

写真を星座のように配置する作品テンプレートで、2つの表示バグが出ていた。写真1枚1枚が星座の「点」にあたるので、以下ではこれをノードと呼びます。

  • 中央に大きく置いた主役写真の言葉が、周りに散らした写真に重なって途中から読めない
  • 左端のタイムライン(サイドバー)の文字が、写真に隠れて切れる

ありがちな直し方と、AI がやった直し方

z-index のバグでよくやりがちなのは「勝たせたい要素の数字を大きくする」。z-index: 999 が生まれる瞬間です。動くこともあるが、なぜ負けていたのかが分からないままなので、次の要素を足したときにまた別の場所で負けます。

今回 AI がやっていた手順は違った。直す前に測っていた。 自分だけでやっていたら、コンソールを開く前に CSS を上から眺めて「たぶんこれが上に来てる」と当たりをつけていたと思う。

js
// ① 2つの要素の「画面上の位置とサイズ」を取り出して、本当に重なっているか数値で確認(幅1200pxのとき)
const a = rail.getBoundingClientRect();   // サイドバー: x:28–172, y:61–192
const b = node.getBoundingClientRect();   // 写真ノード: x:162–354, y:98–368
// → x方向 162–172 が重なっている(10pxの衝突)
 
// ② 重なっている範囲の中から1点(x=167, y=130)を選んで、そこで一番上にいるのは誰かを確認
document.elementFromPoint(167, 130); // → <img>(写真)が返ってきた=サイドバーは本当に下敷き

この重なりが出るのは幅1200〜1256pxの帯だけだった。1200pxでレイアウトが絶対配置に切り替わり、そこから少し広げるとサイドバーと写真が自然に離れます。「いつも壊れている」のではなく「ある幅でだけ壊れる」ので、目視で気づくのが難しい種類のバグでもあった。

測った結果、原因がはっきりした。サイドバーも中心写真も周辺ノードも、全員が z-index: 2 だった。「誰かが大きすぎる」のではなく、序列を誰も決めていなかった。

座標は getBoundingClientRect の戻り値で画面のメモリ。z-index は CSS。DB には保存しない。幅 1200px。両方 z-index: 2。同点は HTML の後が手前サイドバーx:28–172 y:61–192z-index: 2HTML で先下敷き写真ノードx:162–354 y:98–368z-index: 2HTML で後手前測った点 167, 130elementFromPoint写真の img が当たる重なりは x 方向 10px交差であり置き場ではない点線が 10px の衝突。当たり判定は画面のメモリ。

なぜ全員が同じ数字だったかというと、このテンプレートの重なりは元々「紙のテクスチャ → 器 → コンテンツ」の2層しか考えていなかったからだ。文字も写真も等しく「背景より上」でよく、2 はその意味の数字だった。縦に積むレイアウトなら要素同士は重ならないので、これで足りていた。足りなくなったのは、画面が広いときだけ絶対配置に切り替えてノードを散らす作りにしたから。重ならない前提で正しかった数字が、重なるレイアウトに切り替えた瞬間に不足します。バグが広い画面でしか出なかったのはそのためだ。

直し方

手当ては3つに分かれた。

ひとつ目、役割ごとの序列を明示した。読ませたい順に、サイドバー4・中心写真3・周辺ノード2。数字は3段で足ります。999は要りません。

ふたつ目、サイドバーに紙色の下地(rgba(255, 253, 247, 0.86) =ほぼ不透明の86%)を敷いた。序列を決めても、測って分かった10pxの重なりが消えるわけではありません。上に来るようになっただけなので、下地が無いままだと後ろの写真が透けて文字が埋もれます。

みっつ目、周辺ノードの写真の幅を縮めた(12rem→10rem、22vw→17vw)。これは重なったときの見え方ではなく、重なりそのものを減らす手。座標は動かしていない。星の位置はこのテンプレートのデザインそのものなので、そこは触らずに幅だけ詰めた。

z-index と下地と幅は CSS。測った座標は画面のメモリ。保存しない。z-indexサイドバー 4いちばん手前中心写真 3その下周辺ノード 2いちばん奥サイドバーの下地紙色を 86% で敷くrgba 255,253,24710px の重なりは残る後ろの写真が透けない文字が埋もれないbackground。CSS周辺ノードの幅12rem → 10rem22vw → 17vw星の位置は動かさない重なりそのものを減らすwidth。CSS座標は動かしていない効く場所が違う。数字を上げるだけでは足りない。

直した後も同じ測定をもう一度やって、elementFromPoint がサイドバー自身を返すことを確認してから完了にしていた。直す前に測って原因を出し、直した後に同じ測り方で確かめます。この往復が手順として最初から決まっているのが、自分に無かった部分だ。

学んだこと

  • z-index の衝突は「数字が小さい」より「序列を決めていない」ことが原因のことがあります。読ませたい要素には明示的な序列を付けます。縦積みから絶対配置に切り替えている箇所は、序列の付け直しどきになりやすいです。重ならない前提で決めた数字がそのまま残っています
  • 重なりのバグは目視で推理せず、getBoundingClientRect(重なってるか)と elementFromPoint(誰が上か)で測ると、原因も直った証拠も数値で残ります
  • 直し方も1種類ではありません。z-index・背景・サイズは別の層の対策で、z-index だけで押し切ろうとすると数字がインフレします

この記事は2026年7月時点の自分の理解で書いています。仕様や認識が変わってる可能性があります。