スマホサイトやアプリでよく見るUIのドロワーを実装したい。しかしjQueryのメソッドで動かせばスマホでの挙動が重いので、CSS3のアニメーションで実装してみます。ドロワーを実装する方法は他にも色々あると思いますが、そのうちの1つということで。ドロワーをCSS3とjQueryで実装今回実装するドロワーの仕様はこんな内容です。ボタンのタップ、クリックでドロワー表示用クラスをjQueryで付けるドロワー表示時にドロワー以外の領域もしくはボタンをタップ、クリックしたら閉じるIE9以下でのアニメーションは諦め、最低限の機能を提供するようにするプラグインを使わずに、なるべくjQueryでの処理を増やさないというコンセプトなのでHTML構造はちょっと依存します。今回ご紹介する方法で実装したドロワーのデモはこちらで確認できます。画像で説明するとこんな感じです。ボタンを押したらドロワーを表示します。HTML部分まず、HTMLはこのようにします。ドロワーはラッパー要素と分離します。<body> <div id="wrapper"> <div id="header"> <div id="drawer-toggle">ドロワー表示用ボタン</div> </div> <!-- メイン部分。記事や画像などのコンテンツ --> <div class="overlay"></div> </div> <div id="drawer"> <!-- ドロワー部分。メニューとか配置 --> </div></body>クラスoverlayが付いたdivタグは、ドロワーが開いたときにラッパー部分を暗くするために使います。CSS部分CSSで重要な部分はtransitionによるアニメーション指定と、transformのtranslateプロパティによる要素の移動です。肝の部分のみ書くとこんな感じになります。まずラッパー要素。#wrapper { //オーバーレイをabsolute指定するため必要 position: relative; //アニメーションの指定 -webkit-transition: all .2s; -moz-transition: all .2s; -o-transition: all .2s; transition: all .2s;}#wrapper.open { //クラス「open」が付いたら左に280px移動 -webkit-transform: translate3d(-280px, 0, 0); -moz-transform: translate3d(-280px, 0, 0); transform: translate3d(-280px, 0, 0);}/* オーバーレイ表示をするためのCSS */.open .overlay { position: absolute; top: 0; left: 0; //親要素の上に表示するためz-indexを指定 z-index: 9999; //親要素一杯に広がるため縦横のサイズを100%に width: 100%; height: 100%; //暗くするため黒の透明色を指定 background: rgba(0, 0, 0, .5);}次にドロワー要素です。#drawer { position: fixed; //常に固定なのでfixed top: 0; right: -280px; //初期状態では隠すため、横幅のサイズ分だけ画面外に移動する width: 280px; height: 100%; overflow-x: hidden; overflow-y: scroll; -webkit-overflow-scrolling: touch; //スマホでのスクロールに慣性を付ける(デバイス限定 //アニメーション指定 -webkit-transition: all .2s; -moz-transition: all .2s; -o-transition: all .2s; transition: all .2s;}#drawer.open { //クラス「open」が付いたら左へ280px移動(画面内へ移動し表示される) -webkit-transform: translate3d(-280px, 0, 0); -moz-transform: translate3d(-280px, 0, 0); transform: translate3d(-280px, 0, 0);}jQueryでボタンがタップされたら各々の要素にクラス「open」を付けること前提にCSSを書きました。jQuery部分jQueryでは、ボタンが押されたらopenクラスをドロワー要素とラッパー要素に追加する処理をします。jQuery(document).ready(function($) { var $content = $('#wrapper'), $drawer = $('#drawer'), $button = $('#drawer-toggle'), isOpen = false; //ボタンをタップ、クリックした時 $button.on('touchstart click', function () { if(isOpen) { $drawer.removeClass('open'); $content.removeClass('open'); isOpen = false; } else { $drawer.addClass('open'); $content.addClass('open'); isOpen = true; } return false; //親要素へのイベント伝播、aタグのURLクリックによる画面遷移を防ぐ }); //コンテンツ部分をタップ、クリックした時 $content.on('touchstart click', function (e) { e.stopPropagation(); //イベント伝播のみ阻止 if(isOpen) { $drawer.removeClass('open'); $content.removeClass('open'); isOpen = false; } });});変数にあらかじめコンテンツ(#wrapper)、ドロワー(#drawer)、ボタン(#drawer-toggle)を格納して、それらにon()メソッドでスマホとPCでのタッチ、クリックイベントを割り当てています。これでドロワー用クラスのON/OFFが切り替えられるようになり、実装完了です。2014年10月5日追記:コンテンツ部分をタップした時の処理も「return false;」と書いていましたが、これではURLが設定されたaタグをクリックした時にページ移動しなくなってしまうので、イベント伝播のみ阻止するstopPropagation()に変更しました(上記ソースコード23行目)。 IE9以下はプログレッシブ・エンハンスメントで対応IE9以下で通常の動作にならない点はtransitionが効かない他「translateで移動しない」、「オーバーレイのrgbaがIE8以下で効かない」の2つがあります。オーバーレイの半透明色とtransitionのアニメーションは使用上問題無いとして、ドロワーが表示されないのは痛いです。なので、IE9以下には別のCSSで対応してみます。まず、Internet Explorer独自仕様の条件付きコメントを使い、IE9以下だった場合にhtmlタグに専用のクラスを付けます。<!--[if lte IE 9]> <html class="lte-ie9"> <![endif]--><!--[if gt IE 9]><!--> <html> <!--<![endif]-->これでクラス「lt-ie9」をCSSで使えばIE9以下でのみのスタイリングができるようになります。ラッパー要素とドロワー要素でこのクラスを使った結果がこちらです。.lte-ie9 #wrapper.open { //rightでウインドウ右から280pxの位置に移動 right: 280px;}.lte-ie9 #drawer.open { //隠れていたのでrightを使い元に位置に移動 right: 0;}代替案としてそれぞれの要素をrightプロパティで移動させることにしました。これでアニメーションはしませんが、一先ずドロワーを開けるようになり最低限の機能が提供できるようになりました。ドロワー実装時につまづいたこと最後に、このドロワーを作った時につまづいたことを3つ挙げておきます。要素の移動にはtranslate3dを使ったpositionの位置を変更するだけでも可能ですが、アニメーションするときにラッパーとの動作が一致しない(隙間が一瞬できたりする)ことがありました。結果安定感のあるtranslate3dを使っています。translateは3D版を使うtranslateは2D版ならIE9でも動くのですが、スマホ(iphoneのsafariで確認)だと2D版はなぜかドロワー自体が消えたりしました…。fixedとの相性なのでしょうか。IE9ではアニメーションが効きませんし、それならもう3D版に統一しようという結果に。jQueryでのボタンクリックイベントが親要素にも伝播した今回で言えばドロワーを表示するボタンはラッパー要素の中に入っているのですが、このラッパーにもイベントを割り当てていました(ドロワーを閉じる処理)。すると、子要素(ボタン)をクリックしたときに親要素(ラッパー)までもがイベント処理をし始めたんです。どうやらjQueryには親要素にもイベントが伝播する性質があるようで、これにハマりました。親要素にイベントを伝播させないため、またaタグのURLクリックで画面遷移させないために「return false;」を追加しました。参考なった記事はこちらです。子要素のイベント後、親要素のイベントも発生してしまう|teratail1分でわかるreturn false; preventDefault(); stopPropagation() の違い | iwb.jp