position:absolute は位置指定をしていないと親要素の影響で思わぬ動きをするときがある、という話
【追記 ():Flexbox(フレキシブルボックスレイアウト)のことなど、記事の一部を変更しました。】
HTMLをCSSでデザインするときに、要素を重ねることがありますよね。
例えばdivやspan要素、疑似要素:before
や:after
を重ねて一つのデザインを作る、なんてことはよく使われる方法です。
そのときよく使われるCSSがpositionプロパティです。
このプロパティなのですが、親要素と子要素の表示形式やプロパティによって、いろいろややこしかったのです。
このプロパティは、親要素にposition:relative;
、子要素にposition:absolute;
を使い、本来重ねることができない要素を重ねる目的で使用することが多々あります。
例えでいえば、下の様に重ねることです。
.sample-rel1 {
background: #0099ff;
color: #ffffff;
height: 10em;
position: relative;
text-align: center;
width: 10em;
}
.sample-abs {
background: #ff9999;
height: 5em;
opacity:0.7;
position: absolute;
width: 5em;
}
<div class="sample-rel1">
<div class="sample-abs">box2</div>
box1
</div>
重なりが分かりやすいように文字を白色にして、子要素はopacityプロパティで少し透明にしています。
このプロパティを使えば、topプロパティやbottomプロパティといった位置指定をしなければ、親要素の左上の基準位置(top: 0; left: 0;
)に重なって表示されているように見えます。
こようにpositionプロパティは、デザインにおいてとっても便利なCSSプロパティなのですが、このとき、子要素がspanなどのインラインの場合、気をつけなければいけない時があるのです。
親要素の影響を受けるインラインレベルの position:absolute
気をつけなくてはいけない時は、子要素がインラインレベルでtopやrightのプロパティといった表示位置を指定するプロパティが設定されておらず、なおかつ親要素にtext-alignプロパティといった、インラインの位置に影響を与えるプロパティが設定されているときです。
百聞は一見にしかず、下の例を見てください。
/* 上記と同じCSS */
.sample-rel1 {
background: #0099ff;
color: #ffffff;
height: 10em;
position: relative;
text-align: center;
width: 10em;
}
.sample-abs {
background: #ff9999;
height: 5em;
opacity:0.7;
position: absolute;
width: 5em;
}
<!-- 子要素が span -->
<div class="sample-rel1">
<span class="sample-abs">box2</span>
box1
</div>
先ほどのものと違いは子要素がspan要素になっただけです。それだけで表示位置が変わるのです。
特にposition:absolute;
は相対ではなく絶対位置なので、どんな時でも位置指定していなかったら親要素左上の基準位置に来ると思っていたのですが、それはどうやら、わたしの単なる思い込みだったようです。
原因の推測
どうしてこのような表示になるのか、検証をしてみます。
まずテキストを取ってみます。
<!-- テキストなし -->
<div class="sample-rel1">
<span class="sample-abs"></span>
</div>
次にテキストの配置を変えてみます。
<!-- テキストの配置を変更 -->
<div class="sample-rel1">
box1
<span class="sample-abs">box2</span>
</div>
これはどうやら、子要素がインライン要素なので親要素のtext-align
の影響を受けていと推測されます。
position:absolute;
を設定した要素はheightやwidthプロパティを指定して高さや幅を指定できるので、内心ではインラインレベルの子要素はブロックレベルになっていると思っていたのですが、どうやらdisplay:inline-block;
といったインラインレベルのボックスになるようです。
IE11 では他とは違い基準位置に
だが、この仕組みはこれはこれで、コード量の削減や、何らかのハック的な使い道がありそうな気がします。
だけど、そう上手いことはいきません。例えば、もしこのページをIE11から見ていたら、この記事の意味が全く分からないはずです。
なぜなら、他のものと違いIE11では表示のされかたが違うからです。
こちらの画像を御覧ください。
こちらは、先ほどの3つのサンプルを、IE11で表示させた画像です。左から【テキストがないもの】【テキストがあるもの】【テキストの配置を変えたもの】になっています。
見て分かるように、最後のサンプルを除いては、表示位置が左上になっています。
つまり、ブラウザによって表示が違うのです。(2016年7月17日時点)
※サンプルページも用意していますので、ブラウザでの表示はそちらでも確認できます。
リンク:サンプルページ
ここは表示を統一するためにも、何らかの対策を施すべきです。
インラインレベルで位置が変わることへの対応策
この現象を解決するには、二つの方法があります。
1、ブロックレベルにする
問題はposition:absolute;
を設定している要素がインラインレベルなので生じます。ならば、このインラインレベルをブロックレベルに変更したら問題は解決です。
<!-- span をブロックに -->
<div class="sample-rel1">
<span class="sample-abs" style="display: block;">box2</span>
box1
</div>
2、leftプロパティを指定する
または、leftプロパティを指定すると、絶対位置でその位置に表示します。基準位置に表示させるならleft: 0;
を指定すると基準位置になります。
<!-- left を指定 -->
<div class="sample-rel1">
<span class="sample-abs" style="left: 0;">box2</span>
box1
</div>
これでtext-alignプロパティからの影響を避けることができます。
vertical-align や Flexbox はブロックレベルでも影響がでる
これで問題解決すれよかったのですが、そうは問屋が卸しません。
実は前述のように子要素のposition:absolute;
に影響を与える親要素は、他にもあるのです。
例えば下のサンプルを見て下さい。
/* 中央に表示するCSS */
.sample-center1 {
background: #0099ff;
display: table-cell;
height: 10em;
vertical-align: middle;
width: 10em;
}
.sample-center2 {
background: #ff9999;
height: 5em;
opacity:0.7;
width: 5em;
}
<div class="sample-center1">
<div class="sample-center2"></div>
</div>
このように親要素にdisplay:table-cell;
とvertical-align:middle;
を指定すると、子要素のブロックボックスを上下の中央に表示することができるのですが、この状態で、先ほどのようにrelative
とabsolute
を設定してみます。
.sample-rel2 {
background: #0099ff;
color: #ffffff;
display: table-cell;
height: 10em;
position: relative;
vertical-align: middle;
width: 10em;
}
.sample-abs {
background: #ff9999;
height: 5em;
opacity:0.7;
position: absolute;
width: 5em;
}
<div class="sample-rel2">
<div class="sample-abs"></div>
</div>
このように予想もしていないような位置に表示されます。しかも今回は子要素がブロックレベルなのにも関わらず、親要素の影響を受けます。
ちなみに、先ほどのようにテキストを入れてみますと
<!-- テキスト+div -->
<div class="sample-rel2">
box1
<div class="sample-abs">box2</div>
</div>
<!-- div+テキスト -->
<div class="sample-rel2">
<div class="sample-abs">box2</div>
box1
</div>
上記にいたっては完全にテキストが重なります。
また、上記の親要素にline-height: 0;
を設定しますと
と、このようになります。
これらを端的にまとめますと、子要素がブロックレベルであっても、親要素の影響をうけることがあるということなのです。
ちなみにこちらは、IE11も含めた全てのブラウザで表示位置に影響がでたことを確認しています。
また今流行りのFlexbox(フレキシブルボックスレイアウト)でもjustify-content
やalign-items
を使って中央表示ができますが、こちらも位置指定がなければ親要素(Flexbox)の影響を受けます。
最終的な解決策
結局のところ、子要素がインラインであれブロックであれ、親要素の影響を受ける可能性がありますので、最終的には位置を指定するプロパティを設定するのが確実だと言えます。
親要素のCSSを後々変更しないとは限りませんので、そんな時に思いもよらないデザイン崩れが起こる可能性も考えられます。それらも考慮してtop
やleft
といった位置指定するプロパティを使って、ちゃんと位置を指定してあげる必要があります。
.sample-relative {
background: #0099ff;
color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
height: 10em;
position: relative;
width: 10em;
}
.sample-absolute {
background: #ff9999;
height: 5em;
opacity:0.7;
position: absolute;
width: 5em;
top: 0;
left: 0;
}
<div class="sample-relative">
box1
<span class="sample-absolute">box2</span>
</div>
要は、親要素にposition:relative;
、子要素にposition:absolute;
を使ってデザインする場合は、位置を確実にするために子要素に位置指定するプロパティで指定しておいたほうがいいという話でした。