一覧WB-09-1 · CSSレイアウト

CSSの配置と重なり

同じ二つの箱をblock・inline・inline-blockへ変更し、relative・absolute・fixedの座標を測ります。後半では二つの親子グループを重ね、どの要素が手前にあるかを調べます。rectは境界の座標と寸法を小数2桁で返す関数です。

説明

CSSの配置では、通常の流れに沿って場所を確保する要素と、そこから外れて配置される要素があります。positionは位置の基準や場所の確保に関わり、z-indexは重なる順序に関わります。高いz-indexを付けても、親の重なりの範囲を越えられるとは限りません。

同じ二つの箱をblock・inline・inline-blockへ変更し、relative・absolute・fixedの座標を測ります。後半では二つの親子グループを重ね、どの要素が手前にあるかを調べます。rectは境界の座標と寸法を小数2桁で返す関数です。

1blockの通常配置

幅300pxの親へ二つの箱を並べます。

(globalThis.doc=createFormDocument二つの箱を持つ表示文書('<!doctype html><html lang="ja"><head><style>body{font:16px/24px system-ui;margin:16px;background:#111;color:#eee}#container{width:300px;background:#222}.item{padding:4px;background:#22434b}#b{background:#57304a}</style></head><body><div id="container"><div id="a" class="item">A</div><div id="b" class="item">B</div></div></body></html>'),globalThis.a=doc.getElementById("a"),globalThis.b=doc.getElementById("b"),{a:rect("#a")Aの座標と寸法,b:rect("#b")Bの座標と寸法})

二つのblockは縦に並び、親の使える幅を埋めます。後ろのBはAの高さの分だけ下に配置されます。この通常の流れが、位置指定を加える前の基準になります。

2inlineではwidthが同じ意味で効かない

両方をinlineにし、width=120pxも指定します。

(a.style.display=b.style.display="inline"同じ行のインラインへ,a.style.width=b.style.width="120px"幅を指定して効果を測る,{a:rect("#a"),b:rect("#b")})

二つは同じ行へ並びますが、通常の非置換inline要素の幅は120pxにはなりません。内容とインラインの配置によって幅が決まります。widthを書けば、どのdisplayでも同じ大きさになるわけではありません。

3inline-blockで幅を持たせる

displayだけをinline-blockへ変えます。

(a.style.display=b.style.display="inline-block"同じ行に置く箱へ変更,{a:rect("#a"),b:rect("#b")})

それぞれの内容幅120pxに左右paddingが加わり、境界幅は約128pxです。同じ行に置きながら箱として寸法を指定できます。inlineとinline-blockで、同じwidth宣言の結果が異なります。

4relativeは元の場所を残す

Aを通常位置から右へ30px、下へ10pxずらします。

(globalThis.beforeB=rect("#b"),a.style.position="relative"通常位置を基準にする,a.style.left="30px",a.style.top="10px"表示位置をずらす,{a:rect("#a"),bBefore:beforeB,bAfter:rect("#b")Bの場所は維持})

Aの表示位置は変わりますが、Bの座標は変わりません。relativeは通常の流れで確保した場所を残したまま、見える位置をずらします。

5absoluteは通常の流れから外れる

親を位置指定の基準にし、Aをその左上へ置きます。

(doc.getElementById("container").style.position="relative"親を配置の基準にする,a.style.position="absolute"Aを通常配置から外す,a.style.left="0",a.style.top="0"基準の左上へ,{a:rect("#a"),b:rect("#b")})

Aが通常の流れから外れたため、BはAの元の場所へ詰まります。この例ではposition:relativeの親がabsoluteの包含ブロックになります。transformなど、他の指定が包含ブロックを作る場合もあります。

6fixedの基準

Aをfixedへ変え、left=20px、top=30pxとします。

(a.style.position="fixed"表示領域を基準に固定,a.style.left="20px",a.style.top="30px"指定する座標,rect("#a"))

この文書では、Aは表示領域の左から約20px、上から約30pxです。通常はfixedが表示領域を基準にしますが、祖先のtransformなどによって基準が変わる場合があるため、fixedなら常に文書の最上位が基準と決めつけないようにします。

7非表示でも場所が残る場合

Bのvisibilityをhiddenにした場合と、displayをnoneにした場合を比較します。

(b.style.visibility="hidden"場所を残して非表示,globalThis.hiddenRect=rect("#b"),b.style.display="none"レイアウトの箱を作らない,{hidden:hiddenRect,none:rect("#b")})

visibility:hiddenでは箱の寸法が残り、display:noneでは境界の寸法が0になります。見えないという結果が同じでも、周囲のレイアウトへ与える影響は異なります。

8二つの重なりのグループ

ここからは二つの親子グループを重ねます。親Aはz-index=1、親Bは2です。

(doc.querySelector("style").textContent+="#stage{position:relative;width:240px;height:120px}.layer{position:absolute;width:180px;height:90px}.tile{position:absolute;width:120px;height:60px;padding:4px}#layerA{left:0;top:0;z-index:1}#layerB{left:40px;top:20px;z-index:2}#one{z-index:999;background:#22434b}#two{z-index:1;background:#57304a}",doc.body.innerHTML=重なりを調べる二つの親子グループ'<div id="stage"><div id="layerA" class="layer"><div id="one" class="tile">Aの子</div></div><div id="layerB" class="layer"><div id="two" class="tile">Bの子</div></div></div>',{front:hit("#stage",60,40)重なる点の手前の要素,childA:styleOf("#one","z-index"),parentA:styleOf("#layerA","z-index"),parentB:styleOf("#layerB","z-index")})

重なる点で手前にあるのはtwo、つまりBの子です。Aの子の999はAのスタッキングコンテキスト内の順序であり、親Aの1を越えて親Bの2より手前へ出る値ではありません。

9子のz-indexだけを増やす

Aの子を9999へ上げます。

(doc.getElementById("one").style.zIndex="9999"Aの内部で子の順序を上げる,hit("#stage",60,40))

手前はtwoのままです。子の値を大きくするだけでは、別の親のスタッキングコンテキストとの順序を変えられません。

10親の順序を変える

親Aをz-index=3へ上げます。

(doc.getElementById("layerA").style.zIndex="3"親の重なり順を変更,hit("#stage",60,40))

今度はoneが手前になります。二つのグループ間の順序を変えるには、比較されている親側の順序を変更する必要がありました。

11autoでは同じ境界を作らない

親Aのz-indexをautoへ戻します。

(doc.getElementById("layerA").style.zIndex="auto"親の明示的な重なり順を外す,hit("#stage",60,40))

このabsoluteの親Aは、z-index:autoだけでは先ほどのスタッキングコンテキストを作りません。Aの子の9999が外側の比較に関わり、oneが手前になります。

12opacityでも境界ができる

親Aにopacity=0.9を指定します。

(doc.getElementById("layerA").style.opacity="0.9"透明度による重なりの境界,hit("#stage",60,40))

twoが再び手前になります。1未満のopacityもスタッキングコンテキストを作るためです。z-indexだけでなく、祖先のopacityやtransformなども重なりの切り分けで確認します。

まとめ

通常配置で確保する場所と表示位置を分け、relative・absolute・fixedの基準を確認します。

非表示でもvisibilityとdisplayでは場所の残り方が異なります。

重なりはスタッキングコンテキストごとに決まり、子の大きなz-indexだけでは親同士の順序を越えられません。

操作履歴と解説を振り返れます。

ブラウザ / HTML・DOM

1
(globalThis.doc=createFormDocument('<!doctype html><html lang="ja"><head><style>body{font:16px/24px system-ui;margin:16px;background:#111;color:#eee}#container{width:300px;background:#222}.item{padding:4px;background:#22434b}#b{background:#57304a}</style></head><body><div id="container"><div id="a" class="item">A</div><div id="b" class="item">B</div></div></body></html>'),globalThis.a=doc.getElementById("a"),globalThis.b=doc.getElementById("b"),{a:rect("#a"),b:rect("#b")})
{
  "a": {
    "x": 16,
    "y": 16,
    "width": 299.99,
    "height": 31.99
  },
  "b": {
    "x": 16,
    "y": 47.98,
    "width": 299.99,
    "height": 31.99
  }
}
2
(a.style.display=b.style.display="inline",a.style.width=b.style.width="120px",{a:rect("#a"),b:rect("#b")})
{
  "a": {
    "x": 16,
    "y": 14.34,
    "width": 18.46,
    "height": 26.64
  },
  "b": {
    "x": 34.46,
    "y": 14.34,
    "width": 18.19,
    "height": 26.64
  }
}
3
(a.style.display=b.style.display="inline-block",{a:rect("#a"),b:rect("#b")})
{
  "a": {
    "x": 16,
    "y": 16,
    "width": 127.98,
    "height": 31.99
  },
  "b": {
    "x": 143.98,
    "y": 16,
    "width": 127.98,
    "height": 31.99
  }
}
4
(globalThis.beforeB=rect("#b"),a.style.position="relative",a.style.left="30px",a.style.top="10px",{a:rect("#a"),bBefore:beforeB,bAfter:rect("#b")})
{
  "a": {
    "x": 45.99,
    "y": 26,
    "width": 127.98,
    "height": 31.99
  },
  "bBefore": {
    "x": 143.98,
    "y": 16,
    "width": 127.98,
    "height": 31.99
  },
  "bAfter": {
    "x": 143.98,
    "y": 16,
    "width": 127.98,
    "height": 31.99
  }
}
5
(doc.getElementById("container").style.position="relative",a.style.position="absolute",a.style.left="0",a.style.top="0",{a:rect("#a"),b:rect("#b")})
{
  "a": {
    "x": 16,
    "y": 16,
    "width": 127.98,
    "height": 31.99
  },
  "b": {
    "x": 16,
    "y": 16,
    "width": 127.98,
    "height": 31.99
  }
}
6
(a.style.position="fixed",a.style.left="20px",a.style.top="30px",rect("#a"))
{
  "x": 20,
  "y": 30,
  "width": 127.98,
  "height": 31.99
}
7
(b.style.visibility="hidden",globalThis.hiddenRect=rect("#b"),b.style.display="none",{hidden:hiddenRect,none:rect("#b")})
{
  "hidden": {
    "x": 16,
    "y": 16,
    "width": 127.98,
    "height": 31.99
  },
  "none": {
    "x": 0,
    "y": 0,
    "width": 0,
    "height": 0
  }
}
8
(doc.querySelector("style").textContent+="#stage{position:relative;width:240px;height:120px}.layer{position:absolute;width:180px;height:90px}.tile{position:absolute;width:120px;height:60px;padding:4px}#layerA{left:0;top:0;z-index:1}#layerB{left:40px;top:20px;z-index:2}#one{z-index:999;background:#22434b}#two{z-index:1;background:#57304a}",doc.body.innerHTML='<div id="stage"><div id="layerA" class="layer"><div id="one" class="tile">Aの子</div></div><div id="layerB" class="layer"><div id="two" class="tile">Bの子</div></div></div>',{front:hit("#stage",60,40),childA:styleOf("#one","z-index"),parentA:styleOf("#layerA","z-index"),parentB:styleOf("#layerB","z-index")})
{
  "front": "two",
  "childA": "999",
  "parentA": "1",
  "parentB": "2"
}
9
(doc.getElementById("one").style.zIndex="9999",hit("#stage",60,40))
"two"
10
(doc.getElementById("layerA").style.zIndex="3",hit("#stage",60,40))
"one"
11
(doc.getElementById("layerA").style.zIndex="auto",hit("#stage",60,40))
"one"
12
(doc.getElementById("layerA").style.opacity="0.9",hit("#stage",60,40))
"two"

演習完了