読者です 読者をやめる 読者になる 読者になる

納豆、Web、雑記など

position:absolute は位置指定をしていないと親要素の影響で思わぬ動きをするときがある、という話

Web HTML・CSS

HTMLをCSSでデザインするときに、要素を重ねることがありますよね。

例えばdivやspan要素、擬似要素:before:afterを重ねて一つのデザインを作る、なんてことはよく使われる方法です。

そのときよく使われるCSSがpositionプロパティです。

このプロパティなのですが、親要素と子要素の表示形式やプロパティによって、いろいろややこしかったのです。

このプロパティは、親要素にposition:relative;、子要素にposition:absolute;を使い、本来重ねることができない要素を重ねる目的で使用することが多々あります。

例えでいえば、下の様に重ねることです。

box2
box1
/* CSS */

.sample-rel1 {
  background: #0099ff;
  color: #ffffff;
  height: 10em;
  position: relative;
  text-align: center;
  width: 10em;
}
.sample-abs1 {
  background: #ff9999;
  height: 5em;
  opacity:0.7;
  position: absolute;
  width: 5em;
}
<!-- HTML -->

<div class="sample-rel1">
  <div class="sample-abs1">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-abs1 {
  background: #ff9999;
  height: 5em;
  opacity:0.7;
  position: absolute;
  width: 5em;
}
<!-- 子要素が span -->

<div class="sample-rel1">
  <span class="sample-abs1"></span>
</div>


先ほどのものと違いはテキストが無いのと、子要素がspan要素になっただけです。それだけで、表示位置が変わるのです。

特にposition:absolute;は相対ではなく絶対位置なので、どんな時でも位置指定していなかったら親要素左上の基準位置に来ると思っていたのですが、このような予想外の動きをしたので驚きました。

原因の推測

どうしてこのような動きをするのかは、テキストと入れてみたらヒントになるかもしれません。

<!-- span+テキスト -->

<div class="sample-rel1">
  <span class="sample-abs1">box2</span>
  box1
</div>
box2 box1

テキストの配置を変えてみます。

<!-- テキスト+span -->

<div class="sample-rel1">
  box1<span class="sample-abs1">box2</span>
</div>
box1box2

このように絶対位置で何の指定もないと基準位置に行くと思っていたposition:absolute;は、子要素がインラインレベルで何の指定もしていないと、親要素のtext-alignなどで影響を受けるのです。

これはdisplay:inline-block;でも同じように影響を受けるため、子要素がインラインレベルなら生じる現象だと推測されます。

position:absolute;を設定した要素はheightやwidthプロパティを指定して高さや幅を指定できるので、内心では子要素はブロックレベルになっていると思っていたのですが、どうやらdisplay:inline-block;といったインラインレベルのボックスになるようです。

IE11 では他とは違い基準位置に

だが、この仕組みはいわば、position:absolute;を指定した要素は、その要素だけ切り取られ、その場で浮いているようなものです。これは使い道がいろいろありそうな予感がします。

だけど、そう上手いことはいきません。例えば、もしこのページをIE11から見ていたら、この記事の意味が全く分からないはずです。

なぜなら、他のものと違いIE11では表示のされかたが違うからです。

こちらの画像を御覧ください。

IE11で親要素と子要素がinlineでのpposition:absoluteへの影響をテストした画像

こちらは、先ほどの3つのサンプルを、IE11で順番に表示させた画像です。

見て分かるように、最後のサンプルを除いては、表示位置が基準位置になっています。

つまり、ブラウザによって表示が違うのです。(記事作成時点では)

ここは表示を統一するためにも、何らかの対策を施すべきです。

インラインレベルで位置が変わることへの対応策

この現象を解決するには、二つの方法があります。

1、ブロックレベルにする

問題はposition:absolute;を設定している要素がインラインレベルなので生じます。ならば、このインラインレベルをブロックレベルに変更したら問題は解決です。

<!-- span をブロックに -->

<div class="sample-rel1">
  <span class="sample-abs1" style="display: block;">box2</span>
  box1
</div>
box2 box1

2、left プロパティを指定する

または、leftプロパティを指定すると、絶対位置でその位置に表示します。基準位置に表示させるならleft: 0;を指定すると基準位置になります。

<!-- left を指定 -->

<div class="sample-rel1">
  <span class="sample-abs1" style="left: 0;">box2</span>
  box1
</div>
box2 box1

これでtext-alignプロパティからの影響を避けることができます。

vertical-alignはブロックレベルでも影響がでる

これで問題解決すれよかったのですが、そうは問屋が卸しません。

実は前述のように子要素のposition:absolute;に影響を与える親要素は、他にもあるのです。

例えば下のサンプルを見て下さい。

/* 上下中央に表示するCSS */

.sample-box1 {
  background: #0099ff;
  display: table-cell;
  height: 10em;
  text-align: center;
  vertical-align: middle;
  width: 10em;
}

.sample-box2 {
  background: #ff9999;
  height: 5em;
  opacity:0.7;
  width: 5em;
}
<!-- HTML -->

<div class="sample-box1">
  <div class="sample-box2"></div>
</div>

このように親要素にdisplay:table-cell;vertical-align:middle;を指定すると、子要素のブロックボックスを上下の中央に表示することができるのですが、この状態で、先ほどのようにrelativeabsoluteを設定してみます。

/* CSS */

.sample-rel2 {
  background: #0099ff;
  color: #ffffff;
  display: table-cell;
  height: 10em;
  position: relative;
  vertical-align: middle;
  width: 10em;
}

.sample-abs1 {
  background: #ff9999;
  display: block;
  height: 5em;
  opacity:0.7;
  position: absolute;
  width: 5em;
}

このように予想もしていないような位置に表示されます。しかも今回は子要素がブロックレベルなのにも関わらず、親要素の影響を受けます。

ちなみに、先ほどのようにテキストを入れてみますと

<!-- テキスト+div -->

<div class="sample-rel2">
  box1
  <div class="sample-abs2">box2</div>
</div>
box1
box2
<!-- div+テキスト -->

<div class="sample-rel2">
  <div class="sample-abs2">box2</div>
  box1
</div>
box2
box1

上記にいたっては完全にテキストが重なります。

ちなみに、上記の親要素にline-height: 0;を設定しますと

box2
box1

と、このようになります。

これらを端的にまとめますと、子要素がブロックレベルであっても、親要素の影響をうけることがあるということなのです。

ちなみにこちらは、IE11も含めた全てのブラウザで表示位置に影響がでたことを確認しています。

最終的な解決策

結局のところ、子要素がインラインであれブロックであれ、親要素の影響を受ける可能性がありますので、最終的には位置を指定するプロパティを設定するのが確実だと言えます。

つまり、何の位置指定もしていないと下のように表示されます。

box
/* CSS */

.sample-center1 {
  background: #0099ff;
  color: #ffffff;
  display: table-cell;
  height: 10em;
  position: relative;
  text-align: center;
  vertical-align: middle;
  width: 10em;
}
.sample-center2 {
  background: #ff9999;
  display: inline-block; 
  height: 5em;
  opacity:0.7;
  position: absolute;
  width: 5em;
<!-- HTML -->

<div class="sample-center1">
  <div class="sample-center2">box2</div>
  box
</div>

このように、思いのよらない場合に表示されます。

このようなことを防ぐためにも、topleftといった位置指定するプロパティを使って、基準位置に表示させたいとしても、ちゃんと位置を指定してあげる必要があります。

/* CSS */

.sample-center1 {
  background: #0099ff;
  color: #ffffff;
  display: table-cell;
  height: 10em;
  position: relative;
  text-align: center;
  vertical-align: middle;
  width: 10em;
}

.sample-center3 {
  background: #ff9999;
  display: inline-block; /* 削除してもOK */
  height: 5em;
  opacity:0.7;
  position: absolute;
  width: 5em;
  top: 0; /* 追加 */
  left: 0; /* 追加 */
}
<!-- HTML -->

<div class="sample-center1">
  <div class="sample-center3"></div>
  box
</div>
box

要は、親要素にposition:relative;、子要素にposition:absolute;を使ってデザインする場合は、位置を確実にするために子要素に位置指定するプロパティで指定しておいたほうがいいということです。

TOP