auto heightなCSS Transitions
公開:
更新:
要素の高さを0
からauto
までCSSでトランジションさせたいと思って色々試してみました。
試した順に書いていきます。
例として横並びのドロップダウンナビゲーションを使います。
こういうのはユーザビリティ的にどうなのとかそういう話は今回の本題じゃないので置いておきます。
動作サンプルとして違いが分かりやすいので選んだだけです。
HTMLは次のようにしました。最小構成だと大体こんな感じになるかと思います。
<nav class="global">
<ul>
<li class="dropdown">
<a href="#">Lorem</a>
<ul>
<li><a href="#">Lorem ipsum dolor sit amet</a></li>
<!-- 3つまで繰り返し -->
</ul>
</li>
<li class="dropdown">
<a href="#">Ipsum</a>
<ul>
<li><a href="#">Lorem ipsum dolor sit amet</a></li>
<!-- 5つまで繰り返し -->
</ul>
</li>
<li class="dropdown">
<a href="#">Dolor</a>
<ul>
<li><a href="#">Lorem ipsum dolor sit amet</a></li>
<!-- 10個まで繰り返し -->
</ul>
</li>
<li><a href="#">Sit</a></li>
<li><a href="#">Amet</a></li>
</ul>
</nav>
ここからはCSSのみを変更していきます。
.dropdown ul {
height: 0;
overflow: hidden;
transition: 1s linear;
}
.dropdown:hover > ul {
z-index: 1;
height: auto;
}
とりあえず最初に思いつくであろうものはこれでしょうね。
動かしてみましょう。
状態変化が上手くアニメーションしていませんね。
現在の最新版である「CSS Transitionsの2012年4月3日の仕様」を見てみます。
ここの「7. Animatable properties - 7.1. Properties from CSS」にトランジション可能なプロパティと型の一覧があります。height
の欄を見てみるとlength, percentage
となっています。auto
はlengthにもpercentageにも含まれていません。
これは2012年9月20日のEditor’s Draftでも同様です。
それではheight: auto
な高さまでトランジションさせるにはどうすればいいのか。
検索していると、去年の5月にLea Verouがこういうツイートをしているのを見つけました。
なるほど、確かにauto
の高さまで変化しています。
これを踏まえて先ほどのサンプルに実装してみます。
.dropdown ul {
max-height: 0;
overflow: hidden;
transition: 1s linear;
}
.dropdown:hover > ul {
z-index: 1;
max-height: 50em;
}
……何か変です。
開くときは期待通りの動きをしているように見えますが、閉じるときのラグがひどいです。それと、最初のアイテムのアニメーションがどう見ても1秒もかかっていません。
これはトランジションの対象がmax-height
の場合、たとえ実際にはその高さまでなくてもその高さから(または、その高さまで)変化するものとしてトランジションしているのが原因です。
遷移時間が長いのはmax-height
に指定した50em
の高さまで変化するつもりでトランジションしているからで、逆に閉じるときにラグがあるのは50em
の高さから0
までトランジションしているからです。
じゃあ一番大きな3番目に合わせてmax-height
を指定すればいいのでは?
.dropdown ul {
max-height: 0;
overflow: hidden;
transition: 1s linear;
}
.dropdown:hover > ul {
z-index: 1;
/* max-height: 50em; */
max-height: 20em;
}
3番目がまともになっただけで、1番目と2番目にはラグが出ます。
それに、どうせぴったりの高さを指定するならJavaScriptでそれぞれに指定した方がいいです。
とりあえず戻るときのラグだけでもどうにかしたいので、少しいじります。
.dropdown ul {
height: 0; /* 追加 */
max-height: 0;
overflow: hidden;
transition: 1s linear;
}
.dropdown:hover > ul {
z-index: 1;
height: auto; /* 追加 */
max-height: 50em;
}
マウスカーソルが離れたらheight: 0
になるようにし、ラグをなくしてみました。
その影響で閉じるときのトランジションはなくなってしまいましたが、それ以外はほぼ期待通りに動いているように見えます。
しかしまだ問題がありました。2番目と3番目を素早く移動してみて下さい。
3番目のサブメニューが開きっ放しになっている(ように見える)ときがあります。
確かにマウスが離れた時点で高さはゼロになってサブメニューは見えなくなりますが、指定されたtransition
は実行されていて、その途中だとサブメニューが閉じきっていない状態が表示されてしまいます。
「じゃあマウスが乗ったときだけトランジションさせて、離れたときにはそのままheight: 0
にすればいいのでは?」となったので、次のように変更しました。
.dropdown ul {
height: 0; /* 追加 */
max-height: 0;
overflow: hidden;
/* transition: 1s linear; */
}
.dropdown:hover > ul {
z-index: 1;
height: auto; /* 追加 */
max-height: 50em;
transition: 1s linear; /* 追加 */
}
transition
の指定を:hover
時のみに変更しました。
これにより、マウスが離れたときにはトランジションしなくなります。
1つ前のとどちらがいいかは好みなので、使うなら好きなほうを選べばいいと思います。
このサンプルだとこのあたりが妥協点かと思って、ここでやめました。
で、この方法の気になるところですが……
- トランジションの遷移時間が常に
max-height
で指定したものを基準とし、それぞれの高さでは決まらない。これは欠点ではあるけど場合によっては利点になるかもしれない。 - 閉じるときのラグをなくそうとすると、閉じるときのトランジションを実装するのが難しい。ドロップダウンならまだいいが、アコーディオンとかだと致命的な気がする。
- 動作確認がめんどくさい。
- 他にもハマりポイントありそう。
簡単にまとめるとこんな感じです。
こうして試してみる分にはいいですが、実際のサイトで使うにはちょっと不安です。
まあ一度開いたら閉じないようなものなら使い道はあるかもしれませんね。
少し残念ではありますが、こういう動きをCSS Transitions使ってやるなら、大人しくJavaScriptから高さを算出して指定したほうが現実的な気がします。
たとえばこんなふうに。