Unformed Building

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;
}

とりあえず最初に思いつくであろうものはこれでしょうね。
動かしてみましょう。

Auto Height CSS Transition 1

状態変化が上手くアニメーションしていませんね。

現在の最新版である「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がこういうツイートをしているのを見つけました。

(this is probably not new, but just in case…) Q: How to transition to height:auto? A: Transition max-height instead! jsfiddle.net/leaverou/zwvNY/

@LeaVerou https://twitter.com/LeaVerou/status/69776299685715970

なるほど、確かにautoの高さまで変化しています。
これを踏まえて先ほどのサンプルに実装してみます。

.dropdown ul {
  max-height: 0;
  overflow: hidden;
  transition: 1s linear;
}

.dropdown:hover > ul {
  z-index: 1;
  max-height: 50em;
}

Auto Height CSS Transition 2

……何か変です。
開くときは期待通りの動きをしているように見えますが、閉じるときのラグがひどいです。それと、最初のアイテムのアニメーションがどう見ても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;
}

Auto Height CSS Transition 3

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;
}

Auto Height CSS Transition 4

マウスカーソルが離れたら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; /* 追加 */
}

Auto Height CSS Transition 5

transitionの指定を:hover時のみに変更しました。
これにより、マウスが離れたときにはトランジションしなくなります。
1つ前のとどちらがいいかは好みなので、使うなら好きなほうを選べばいいと思います。


このサンプルだとこのあたりが妥協点かと思って、ここでやめました。
で、この方法の気になるところですが……

  • トランジションの遷移時間が常にmax-heightで指定したものを基準とし、それぞれの高さでは決まらない。これは欠点ではあるけど場合によっては利点になるかもしれない。
  • 閉じるときのラグをなくそうとすると、閉じるときのトランジションを実装するのが難しい。ドロップダウンならまだいいが、アコーディオンとかだと致命的な気がする。
  • 動作確認がめんどくさい。
  • 他にもハマりポイントありそう。

簡単にまとめるとこんな感じです。
こうして試してみる分にはいいですが、実際のサイトで使うにはちょっと不安です。
まあ一度開いたら閉じないようなものなら使い道はあるかもしれませんね。

少し残念ではありますが、こういう動きをCSS Transitions使ってやるなら、大人しくJavaScriptから高さを算出して指定したほうが現実的な気がします。
たとえばこんなふうに