マウスオーバーすると親要素の高さが変化する 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-sizing
はborder-box
でfloat
が指定されている
という条件を満たす要素の孫以下の要素で、
- マウスオーバー時に見た目の変化がある
と、 #wrapper
に当たる要素の高さが、 padding-top
+ padding-bottom
+ border-top-width
+ border-bottom-width
の分だけ伸びる模様。なんともややこしい……。なお、コンテンツ量が min-height
を超えるほどあった場合、バグは発生しない。こういう複雑な条件を満たさないと発現しないバグは、気をつけていてもなかなか取りきれない。 min-height
と box-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 }); } });
どうしても他に対応策がないときの最終手段といえましょう。