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

カラーミーショップの有料テンプレート(MONOを除く)はスマートフォン閲覧時のメニューがシンプルなので、もうすこし作り込みたいことがあります。

一方、MONOテンプレートにはドロワーメニュー(横から引き出てくるタイプ)があらかじめ実装されています。現在一番よく使われているスマートフォン用のメニューではないでしょうか。

今回はドロワーメニューの実装について書きました。
MONOテンプレート以外を使っていて、スマートフォン用メニューを改善したい方は参考にどうぞ。

スマートフォンショップ用テンプレートを参考にする

ドロワーメニューのコードは、MONOテンプレートやスマートフォンショップ用テンプレートの共通HTML・CSS内にあります。
ここではスマートフォンショップ用の無料テンプレート「PLAIN」を参考にします。

PLAINデモサイト



必要なコードを一つずつ見ていきます。
行数は一致しないこともありますので、目安程度。

共通HTML 302-419行目
ドロワーメニュー本体。メニュー項目を増減させる場合はここを修正します。長文ではありますが、コメント行が付いていますので、どこで何をやっているか大体わかると思います。

  <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行目
オーバーレイ部分(半透明の黒い背景)です。

<div id="overlay" class="overlay"></div>


共通HTML 16-20行目
クリック時にオープンするボタン。後述のjQueryのクリックイベント(.u-toggle)が発火します。

            <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; がポイントになります。

.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行目
オーバーレイ部分のスタイル。オーバーレイの黒色はここで変えられます。

.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が必要です。

/////// ドロワー ///////
$(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)には以下のようなプロパティを設定してありますので、スムーズなスクロールをします。

-webkit-overflow-scrolling: touch;

上記スタイルを入れたら入れたで、今度はページ最上部・最下部でバウンドする挙動時に不具合が出て、バウンド後3秒程度操作できなくなります。
ドロワーメニュー本体のクラス(.drawer)のposition: fixed;との相性がよくないらしいんですが、これは回避しようがないです。

ドロワーメニューの裏側にあるコンテンツが動く

ドロワーメニューは本来のページの上に、レイヤーとして重なる形で表示されます。
MONOテンプレートやPLAINテンプレートは、ドロワーメニューをスクロールさせるつもりでフリックしていても、本来のページがスクロールしていることがあります。
ドロワーメニュー展開中は本来のページを固定するコードを追加で入れておくと、怪しい挙動を回避できます。

本来のページを動かさないように、bodyを固定する.fixedを付けたり取ったりする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について記事にしています。

ご相談・お問い合わせはこちら