スマホサイトやアプリでよく見る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;」を追加しました。参考なった記事はこちらです。
子要素のイベント後、親要素のイベントも発生してしまう|teratail
1分でわかるreturn false; preventDefault(); stopPropagation() の違い | iwb.jp