マウスオーバーすると親要素の高さが変化する IE のバグ

a タグにマウスを重ねると、それを内包する要素の高さがベコッと広がったまま戻らなくなるバグは、古代より受け継がれてきた由緒ある IE のバグだ。「IE hover 高さ」などでググると、このバグに悩まされている人たちがたくさん引っかかる。このバグは、 IE 10 の現代に於いても現存していて、生きた化石と言ってもよいかと思う。

このバグは再現条件が限られるし、厄介な割にはつい忘れがちなものなので、プロジェクトも終わりに差し掛かっているところで発見されることも少なくない。しかも色々な組み合わせで発生するみたいで、どのサイトも書いてある再現条件が違う。今回は全然違った条件で引っかかりました。以下が再現された HTML 。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>IEでガタつく</title>
<style>
  #wrapper {
    width: 200px;
    padding-bottom: 50px;
    min-height: 100px;
    box-sizing: border-box;
    float: left;
  }

  a:hover {
    color: red;
  }
</style>
</head>
<body>
<div id="wrapper">
  <div>
    <a href="#">hogehoge</a>
  </div>
</div>
<div style="clear:left;">-----------------------------</div>
</body>
</html>

発生条件

書かれているスタイル指定はすべて再現条件に含まれます。つまり、

  • 幅が指定されていて
  • 上下の padding もしくは border がどちらか一方、または両方指定されていて
  • min-height が指定されていて
  • box-sizingborder-box
  • float が指定されている

という条件を満たす要素の孫以下の要素で、

  • マウスオーバー時に見た目の変化がある

と、 #wrapper に当たる要素の高さが、 padding-top + padding-bottom + border-top-width + border-bottom-width の分だけ伸びる模様。なんともややこしい……。なお、コンテンツ量が min-height を超えるほどあった場合、バグは発生しない。こういう複雑な条件を満たさないと発現しないバグは、気をつけていてもなかなか取りきれない。 min-heightbox-sizing: border-box は同時に指定する時は要注意ということだ。

解決策

いくつかの解決策がある。

発現条件を回避する

一番わかりやすい解決方法は、 #wrapper に当たる要素から、発現条件であるプロパティのいずれかを除去すること。 box-sizing は必須じゃないかもしれないし、 padding-top, padding-top は別の要素のマージン等で代用できるかもしれない。しかし、 HTML 構造によってはこういう融通を利かせられないことがある。

::before, ::after を使う

これはなかなかスマートな手法かもしれない。 ::before::after の擬似要素を用いることで、上下 padding の指定を除去しようとする試み。

#wrapper {
  width: 200px;
  /* padding: 20px 0 30px; */
  padding: 0; /* 上下 padding の指定は無し */
  min-height: 100px;
  box-sizing: border-box;
  float: left;
}

#wrapper:before {
  content: "";
  display: block;
  height: 20px;
}

#wrapper:after {
  content: "";
  display: block;
  height: 30px;
}

これが一番楽かもしれない。だけど、上下に border が指定されていた場合は使えないですね。

JS でむりやり

かなりアホンダラなやり方。自分はこのやり方を試して、いちおう出来たけど、その後 ::before, ::after を使う手法を思いついたので採用しなかった。当然、ピクセルを使ってレイアウトしている時に限る。

$(function () {
  var $main, paddingTop, paddingBottom, borderTopWidth, borderBottomWidth, minHeight;
  if (isIE) {
    $main = $('#wrapper');
    paddingTop = parseInt($main.css('paddingTop'), 10);
    paddingBottom = parseInt($main.css('paddingBottom'), 10);
    borderTopWidth = parseInt($main.css('borderTopWidth'), 10);
    borderBottomWidth = parseInt($main.css('borderBottomWidth'), 10);
    minHeight = parseInt($main.css('minHeight'), 10);
    $main.css({
      'box-sizing': 'content-box',
      'width': $main.width(),
      'min-height': minHeight - paddingTop - paddingBottom - borderTopWidth - border-BottomWidth
    });
  }
});

どうしても他に対応策がないときの最終手段といえましょう。