auto height な CSS Transitions

Category : CSS

要素の高さを 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 properties7.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 から高さを算出して指定したほうが現実的な気がします。
たとえばこんなふうに

Leave a Reply