カラーミーテンプレートにスマホ用ドロワーメニューを実装する

カラーミーショップの有料テンプレート(MONOを除く)はスマートフォン閲覧時のメニューがシンプルなので、もうすこし作り込みたいことがあります。
そういう仕事のご依頼も多いです。
一方、MONOテンプレートにはドロワーメニュー(横から引き出てくるタイプ)があらかじめ実装されています。現在一番よく使われているスマートフォン用のメニューではないでしょうか。
今回はドロワーメニューの実装について解説します。
MONOテンプレート以外を使っていて、スマートフォン用メニューを改善したい方は参考にどうぞ。
スマートフォンショップ用テンプレートを参考にする
ドロワーメニューのコードは、MONOテンプレートやスマートフォンショップ用テンプレートの共通HTML・CSS内にあります。
ここではスマートフォンショップ用の無料テンプレート「PLAIN」を参考にします。
PLAINデモサイト

必要なコードを一つずつ見ていきます。
行数は一致しないこともありますので、目安程度。
共通HTML 302-419行目
ドロワーメニュー本体。メニュー項目を増減させる場合はここを修正します。長文ではありますが、コメント行が付いていますので、どこで何をやっているか大体わかると思います。
共通HTML
<aside id="drawer" class="drawer">
<!-- 閉じるボタン -->
<div class="drawer__item drawer__item--close">
<a href="" class="u-close">
<i class="fa fa-close fa-lg"></i>
</a>
</div>
<!-- // 閉じるボタン -->
<!-- ホームへ戻る -->
<div class="drawer__item">
<a href="<{$home_url}>" class="drawer__item__name">
<span class="drawer__item__name__icon--left"><i class="fa fa-home fa-fw fa-lg"></i></span>
<span class="drawer__item__name__text">ホームへ戻る</span>
<span class="drawer__item__name__icon--right"><i class="fa fa-chevron-right fa-fw"></i></span>
</a>
</div>
<!-- // ホームへ戻る -->
<!-- カテゴリーリスト -->
<{section name=num loop=$category}>
<{if $smarty.section.num.first}>
<div class="drawer__item drawer__item--accordion u-accordion">
<a class="drawer__item__name u-accordion__name">
<span class="drawer__item__name__icon--left"><i class="fa fa-list-ul fa-fw fa-lg"></i></span>
<span class="drawer__item__name__text">商品ジャンルから探す</span>
<span class="drawer__item__name__icon--right"><i class="fa fa-chevron-down fa-fw"></i></span>
</a>
<ul>
<li class="linklist__item">
<a href="<{$home_url}>?mode=srh&cid=&keyword=">
<span class="linklist__item__text">すべての商品</span>
</a>
</li>
<{/if}>
<li class="linklist__item">
<a href="<{$category[num].link_url}>">
<span class="linklist__item__text"><{$category[num].name}></span>
</a>
</li>
<{if $smarty.section.num.last}>
</ul>
</div>
<{/if}>
<{/section}>
<!-- // カテゴリーリスト -->
<!-- グループリスト -->
<{section name=num loop=$group}>
<{if $smarty.section.num.first}>
<div class="drawer__item drawer__item--accordion u-accordion">
<a class="drawer__item__name u-accordion__name">
<span class="drawer__item__name__icon--left"><i class="fa fa-tags fa-fw fa-lg"></i></span>
<span class="drawer__item__name__text">キーワードから探す</span>
<span class="drawer__item__name__icon--right"><i class="fa fa-chevron-down fa-fw"></i></span>
</a>
<ul>
<{/if}>
<li class="linklist__item">
<a href="<{$group[num].link_url}>">
<span class="linklist__item__text"><{$group[num].name}></span>
</a>
</li>
<{if $smarty.section.num.last}>
</ul>
</div>
<{/if}>
<{/section}>
<!-- // グループリスト -->
<!-- 特商法 -->
<div class="drawer__item">
<a href="<{$sk_url}>" class="drawer__item__name">
<span class="drawer__item__name__icon--left"><i class="fa fa-question-circle fa-fw fa-lg"></i></span>
<span class="drawer__item__name__text">このショップについて</span>
<span class="drawer__item__name__icon--right"><i class="fa fa-chevron-right fa-fw"></i></span>
</a>
</div>
<!-- // 特商法 -->
<!-- お問い合わせ -->
<div class="drawer__item">
<a href="<{$view_inq_url}>" class="drawer__item__name">
<span class="drawer__item__name__icon--left"><i class="fa fa-envelope fa-fw fa-lg"></i></span>
<span class="drawer__item__name__text">お問い合わせ</span>
<span class="drawer__item__name__icon--right"><i class="fa fa-chevron-right fa-fw"></i></span>
</a>
</div>
<!-- // お問い合わせ -->
<!-- アカウント関連 -->
<{if $members_use_flg}>
<div class="drawer__item drawer__item--accordion u-accordion">
<a class="drawer__item__name u-accordion__name">
<span class="drawer__item__name__icon--left"><i class="fa fa-user fa-fw fa-lg"></i></span>
<span class="drawer__item__name__text">アカウント</span>
<span class="drawer__item__name__icon--right"><i class="fa fa-chevron-down fa-fw"></i></span>
</a>
<ul>
<{if !$members_login_flg}>
<{if $members_register_flg}>
<li class="linklist__item">
<a href="<{$members_regi_url}>">
<span class="linklist__item__text">会員登録</span>
</a>
</li>
<{/if}>
<li class="linklist__item">
<a href="<{$members_login_url}>">
<span class="linklist__item__text">ログイン</span>
</a>
</li>
<{else}>
<li class="linklist__item">
<a href="<{$members_login_url}>">
<span class="linklist__item__text">ログアウト</span>
</a>
</li>
<{/if}>
</ul>
</div>
<{/if}>
<!-- // アカウント関連 -->
</aside>
共通HTML 421行目
オーバーレイ部分(半透明の黒い背景)です。
共通HTML
<div id="overlay" class="overlay"></div>
共通HTML 16-20行目
クリック時にオープンするボタン。後述のjQueryのクリックイベント(.u-toggle)が発火します。
共通HTML
<a href="" class="u-toggle">
<div class="u-wrap">
<i class="fa fa-bars fa-lg"></i><span>メニュー</span>
</div>
</a>
共通CSS 474-498行目
openというクラスを付けたり取ったりすることで、ドロワーメニュー本体を表示したり、画面外に出したりしています。
position: fixed; と left: -280px; がポイントになります。
共通CSS
.drawer.open {
left: 0;
}
.drawer {
position: fixed;
top: 0;
left: -280px;
z-index: 9999;
overflow-x: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
width: 280px;
height: 100%;
background: #fff;
color: #212121;
-moz-transition-property: all;
-o-transition-property: all;
-webkit-transition-property: all;
transition-property: all;
-moz-transition-duration: 0.3s;
-o-transition-duration: 0.3s;
-webkit-transition-duration: 0.3s;
transition-duration: 0.3s;
}
共通CSS 1046-1055行目
オーバーレイ部分のスタイル。オーバーレイの黒色はここで変えられます。
共通CSS
.overlay {
position: fixed;
z-index: 2000;
top: 0;
left: 0;
display: none;
width: 100%;
height: 120%;
background-color: rgba(0, 0, 0, 0.75);
}
utils.js 内
クリック時にopenというクラスを付けたり取ったりするjQueryコードです。
セレクタ #drawer、.u-toggle、.u-close、#overlayを指定しています。
設置の際はHTML内に最低4つの id・classが必要です。
utils.js
/////// ドロワー /////// $(function(){ var $drawer = $('#drawer'), $button = $('.u-toggle'), isOpen = false; $button.on('touchstart click', function () { if(isOpen) { $drawer.removeClass('open'); isOpen = false; } else { $drawer.addClass('open'); isOpen = true; } $("#overlay").fadeIn("fast"); return false; }); $('.u-close, #overlay').on('touchstart click', function (e) { e.stopPropagation(); if(isOpen) { e.preventDefault(); $drawer.removeClass('open'); $("#overlay").fadeOut("fast"); isOpen = false; } }); });
必要なコードは以上です。
実際のテンプレートにはドロワーメニューと関係のないコードが多いので理解しにくいですが、こうしてみると案外難しくありません。
あとは、レイアウトするスタイルを入れたり、ドロワーメニュー以外の機能を入れて終わりです。
iPhone(iOS)+Safari の不具合情報
スクロールに応じて、画面上部のアドレスバーが大きく表示されたり・小さく表示されたり、画面下部のツールバーが出たり入ったりすることが原因で、思った通りに動作しないことがよくあります。
同様の原因で、position: fixed;で付ける上固定のヘッダーメニューでも崩れます。自分で調べられる人でないと対応するのが難しくなります。
慣性付きスクロールを使用するか
MONOテンプレートのドロワーメニューは、スムーズなスクロールをしません。CSSにスタイルを入れる必要があります。
今回例に挙げたPLAINのドロワーメニュー本体のクラス(.drawer)には以下のようなプロパティを設定してありますので、スムーズなスクロールをします。
CSS
-webkit-overflow-scrolling: touch;
上記スタイルを入れたら入れたで、今度はページ最上部・最下部でバウンドする挙動時に不具合が出て、バウンド後3秒程度操作できなくなります。
ドロワーメニュー本体のクラス(.drawer)のposition: fixed;との相性がよくないらしいんですが、これは回避しようがないです。
ドロワーメニューの裏側にあるコンテンツが動く
ドロワーメニューは本来のページの上に、レイヤーとして重なる形で表示されます。
MONOテンプレートやPLAINテンプレートは、ドロワーメニューをスクロールさせるつもりでフリックしていても、本来のページがスクロールしていることがあります。
ドロワーメニュー展開中は本来のページを固定するコードを追加で入れておくと、怪しい挙動を回避できます。
本来のページを動かさないように、bodyを固定する.fixedを付けたり取ったりするjQueryを書きます。
jQuery
$(function() {
$('.u-toggle').on('touchstart click', function() {
scrollPos = $(window).scrollTop();
$('body').addClass('fixed').css({'top': -scrollPos});
});
$('.u-close, #overlay').on('touchstart click', function() {
$('body').removeClass('fixed').css({'top': 0});
window.scrollTo(0, scrollPos);
});
});
CSS
body.fixed {
position: fixed;
left: 0;
right:0;
}
本来はbodyをoverflow-y: hidden; とするところですが、iPhone時に動作しない不具合があるので、position: fixed; として固定します。
bodyをposition: fixed; すると、本来のページのスクロール位置が先頭に変わってしまうので、scrollPosでトップからの位置を把握して調整しています。
scrollPosは下で参照するので、グローバル変数にしておく(var 付けない)。
おわりに
Androidは基本的に問題は起きませんが、iOSの場合のスマホメニューは鬼門で、わりと不具合が出ます。
実機で確認しておかないといけない所で、トラブると手間のかかる所です。
執筆者

えいじ@naeco.jp この記事を書いた人
自作するのが好きですぐに試したくなる、凝り性なWebエンジニア。
カラーミーショップ、モールなどのECについて記事にしています。
ご相談・お問い合わせはこちら