2.IDEA

JP only

UIアニメーション実装の最適解、CSS Transitions+class操作を使おう

スライド式メニューのような、「あるイベントが起こるとアニメーションした後にある状態に変わるもの」を実装する方法はいくつかあります。
jQueryのanimate()、JavaScriptのsetTimeout()などあるかと思いますが、私はCSS Transitions + class操作をオススメしたいです。軽量かつ簡単だからです。

CSS Transitionsとclass操作を使ったアニメーションについて

ざっくり言うと、「classを追加/削除してプロパティの値を変えるときに、transitionを使えばアニメーションできるよ」ということです。

CSS Transitionsについて

transitionのイメージ画像

あるプロパティにtransitionを指定すると、そのプロパティの値が変化したとき、値の変化がアニメーションで補間されるようになります。
この「変化」のきっかけはなんでも良いです。よく使われるのは:hoverによる値の変化ですが、以下のような場合でもアニメーションが発生します。

  • classを追加/削除したことでプロパティの値が変化したとき
  • JavaScriptで直接プロパティの値を変化させたとき
  • Firebug等のブラウザの開発ツールでプロパティの値をいじったとき
  • windowをリサイズしてメディアクエリーで指定したブレイクポイントを通過したとき(幅によって違う値を指定している場合)

他にもあるかもしれませんが、私が思いつく限りで挙げてみました。

class操作について(classie.jsが便利)

classを追加したり削除したりするために、よくjQueryを使うかと思います。

しかし、今回提案する方法で最も重要なことは「軽量さ」です。なので、jQueryを使わずにclass操作を行う必要があります。

そこで、classie.jsを使います。
classieとは、クラス操作に特化した超軽量なライブラリです(85行、1.973kb)。
Bonzoという(jQueryによく似た)JavaScriptライブラリから、class操作に関するものだけ抽出したもののようです。

以下のような使い方をします(というかこれがclassieのすべての機能です)。

classie.has( el, 'my-class' ) // trueかfalseを返す
classie.add( el, 'my-new-class' ) // classを追加
classie.remove( el, 'my-unwanted-class' ) // classを削除
classie.toggle( el, 'my-class' ) // トグル操作(指定したclassが無ければ追加し、あれば削除)

追記:<code>タグを使うことでclass操作を可能にするuilangというものがありました。アニメーションはtransitionでやっているようです。サイズも軽量で良さげです。

実装方法

材料が揃ったので実装してみましょう。以下、実装例です。

<link href='/asset/css/main.css' rel='stylesheet' type='text/css'>

<a id="hamburger"></a>
<div id="sidebar" class="sidebar">
  <ul>
    <li><a href="#">Home</a></li>
    <li><a href="#">About Us</a></li>
    <li><a href="#">Recent Posts</a></li>
  </ul>
</div>

<script type="text/javascript" src="/lib/js/classie.js"></script>
<script type="text/javascript" src="/asset/js/main.js"></script>
/* main.css */

.sidebar{
  width: 200px;
  margin-left: -200px; /* 左に隠します */
  -webkit-transition: margin-left .25s ease;
  transition: margin-left .25s ease;
  position: fixed;
  height: 100%;
  left: 0;
  top: 0;
}
.sidebar--open{
  margin-left: 0; /* 現れます */
}
/* main.js */

var hamburger = document.getElementById("hamburger"),
    sidebar = document.getElementById("sidebar");

/* IE8以下にも対応させたいならattachEventも同時に使用します */
hamburger.addEventListener('click', function(){
  classie.toggle( sidebar, 'sidebar--open' ); /* sidebar--openをトグルします */
}, false);

「サイドバー」だけでなく、「開閉式ボックス」もおまけに実装しました。

.sidebar--openというclassを追加/削除することで、margin-leftの値を変えています。
transitionが使えないIE9以下でも、アニメーションはしないものの「開閉」という機能自体は使えます

開閉式ボックスについては、querySelectorAll()を使っているので、IE7以下は動作しません。対応したい場合はgetElementById()を使うと良いでしょう。

追記:スマホのアニメーションにはmargin-leftではなく、googleのスライドメニューのように、GPUアクセラレーションできるtransformを使った方が良いかと思います。

transitionとclass操作を使う上でのメリット

使う上でのメリットは主に3つあります。

  • 軽量
  • コードがシンプル
  • 状態が見やすい、管理しやすい

先ほど紹介したコードを見てもらうとわかるように、とても軽量かつシンプルに実装出来ます。setTimeout()を使うのはなんかめんどくさいし、jQueryだとサイズが膨大すぎる」と思っている人にはちょうど良い方法だと思います。

また、変化前と変化後のプロパティの値をすべてCSSに記述出来るので、それぞれの状態を管理しやすいというメリットもあります。

transitionとclass操作を使う上でのデメリット

使う上でのデメリットは主に2つあります。

  • 幅や高さなどがautoなものには使えない(使いにくい)
  • ページ内で何度も使われるコンポーネントには使いにくい(IE7以下を切り捨てるなら問題ない)

残念なことに、transitionは「autoから○○pxまで」や「○○%からautoまで」といった指定には対応していません先ほどのデモの開閉式ボックスもその例なんですが、heightをいじらず、「ボックスの中の要素の高さを取得して、高さ分だけtopにネガティブマージンをとって隠す」という方法で実装しています(コードは割と煩雑になっています...)。

後者はjQueryとの比較におけるデメリットなんですが、IE7以下にも対応した簡単な要素の取得方法がない、ということです。HTMLにidを自動割り当てするしくみを作ってgetElementById()するとそこそこ楽なのかもしれません。

CSS Transitionsとグレースフルデグラデーション

CSS3のプロパティであるtransitionを使った方法を紹介しました。

CSS3のプロパティを使うと、「古いIEじゃ動かないじゃん」という指摘をする人がいます。しかし、私は、レガシーブラウザには最低限の機能さえ提供してあげれば良いと考えています。グレースフルデグラデーションってやつです。

グレースフルデグラデーションとは「モダンブラウザを基準に機能を実装して、レガシーブラウザには最低限の機能を用意する」という手法のことです。transitionは、「ある状態からある状態へ移る」という「機能」に「アニメーション」という「装飾」を付けているだけですので、レガシーブラウザでも問題なく情報を提供できます。

少し個人的な話になりますが、私は、インターネットやコンピュータに特に関心があるわけではない人たちが顧客のサービスのウェブ担当者をしています。そのウェブサイトの流入を見てみると、transitionが使えないIE9以下の流入数の割合はブラウザ全体の3%未満でした。また、インターネットに関心のある人が閲覧するであろう、とあるウェブサービスのサイトでは0.1%ほどと、ごく少数でした。

このデータを見ると、わざわざ膨大なサイズのライブラリを使ってまで、レガシーブラウザにモダンブラウザと遜色ない体験を提供するのが馬鹿らしくなってきます。そもそもそのくらいレガシーなブラウザを使っているということは、動作も快適ではないだろうから、アニメーションなんかするともっと酷くなる気がします。

このブログでは、html5shivを使わない方法SVGのIE対応などについて書いてきましたが、共通して伝えたいメッセージは、「モダンブラウザを犠牲にしてまでIEに対応する必要は無い」です。

変化の激しいウェブ界においては、IEなどのレガシーブラウザの対応にとらわれすぎず、ウェブの未来を見据えたコーディングをすることが大切だと私は思います。

2016年の1月に、IEのサポートサイクルが変わることが発表されました。2016年には、IE8が今のIE6のような扱いになっていることでしょう。想像するだけでワクワクしますね!

参考