スマートフォン用ドロワーメニューの作成方法〜レスポンシブ対応編

HTML&CSS

スマートフォン用のドロワーメニューの簡単な例は前回紹介しましたが、少し複雑な動きに対応させるには多少問題があります。
今回は起こり得る問題点と解決方法について考えてみます。サンプルとして前回使用したドロワーメニューを使用します。

スマートフォン用ドロワーメニュー学習用簡単版

ドロワーメニュー修正サンプル(完成版)

スポンサーリンク

ドロワーメニューで起こりうる問題点

上のサンプルを少し発展させてレスポンシブに対応させてみます。

ドロワーメニューレスポンシブ対応版サンプル1

実際にこのサンプルを表示させてみましょう。
メディアクエリの切り替えポイントは736pxです。735px以下ではスマートフォン用のナビゲーションになり、それ以上の場合はPC用になります。

スマートフォン用では以前作成したハンバーガーボタンをクリックするとドロワーメニューが開閉します。
sp1
PC用ではヘッダー内でメニューが横並びに整列して画面中央に配置されます。
pc1

問題点1

一見問題ないように思えますが、PC上でブラウザの幅を縮めると変なアニメーションがついてナビゲーションが移動します。今度はブラウザの幅を広げてみるとやっぱり変なアニメーションが起こります。メニューが慌てて移動しているようであまり見栄えの良いものではありません。
これが第1の問題点です。

原因

レスポンシブに対応するためにメディアクエリを使用しています。メディアクエリでナビゲーションの形態を変更する仕組みなっていますから切り替えポイントを超えたところでトリガーが発生します。つまりブラウザの幅を変更することでtransitionに対して動けと指示が出てしまっているのです。メディアクエリをトリガーにすることで、面白いアニメーションを設定できる反面、意外なところで予期しない影響が出てしまいます。この現象は起こりやすいものとして認識しておく必要があると思います。

けれども、PC上でブラウザの幅を変えた場合は確かに問題になりますが、そもそも、主要目的のスマートフォン閲覧時とPC閲覧時という切り分けでは問題は起こりません。スマートフォンで閲覧した場合は最初からドロワーメニューですし、PCの場合は最初から中央に整列したシンプルなナビゲーションです。妥協すればこれで良いかもしれませんが、PC閲覧者がブラウザの幅を変更することは十分に考えられます。この時の問題をどう捉えるかですね。

対策

メディアクエリのトリガーを中止することができませんので、jQueryで対処します。
jQueryでアニメーションが必要な時に動的にアニメーションの設定を書き出す方法を取ってみました。
この方法だとわざわざCSS3のtransitionを使用せずともjQueryでアニメーションさせれば良いのではないかとジレンマに陥りますが。。。
アニメーション部分はCSS3のtransitionに仕事をしてもらうという考え方です。

問題点2

次にスマートフォン用のサイズにして実際にメニューをクリックしてみます。今度のサンプルは長いページを想定しており、リンク先はページ内リンクでidを指定しています。この場合リンク先に移動できてもメニューは勝手に閉じてくれません。これが第2の問題点です。
また、ページ内リンクで移動した場合には、表示される内容の見出しが固定したヘッダーの下に隠れてしまう問題もあります。

原因

原因はli要素に対してクリックしたらメニューを閉じる命令を指定していないからです。

対策

li要素にメニューを閉じる命令を行うことです。「.gnav li」に対してクリックイベントが起こるとナビゲーションを閉じる命令をjQueryで記述します。
移動後に見だしがヘッダーに隠れる問題を解決するために、スクロールするとヘッダーが消える仕組みにします。
そうすることで表示面積が広がりコンテンツの内容を少しでも広く取ることができます。

以上の内容を踏まえて以下のようにサンプルを書き換えました。

ドロワーメニュー修正サンプル

HTMLコードでの変更はモーダルウインドウを作成する空のdiv.modalをHTMLの一番最後に記載していたものをjQueryで動的に作成するようにしています。

ナビゲーションのリンクの設定をid名を指定したページ内リンクにしました。

HTMLコード

  <header>
    <span>Header</span>
  </header>
  <div class="gnav-btn">
    <div class="icon-animation">
      <span class="top"></span>
      <span class="middle"></span>
      <span class="bottom"></span>
    </div>
  </div>

  <nav class="global">
    <ul class="gnav">
      <li><a href="#sec1">menu1</a></li>
      <li><a href="#sec2">menu2</a></li>
      <li><a href="#sec3">menu3</a></li>
      <li><a href="#sec4">menu4</a></li>
      <li><a href="#sec5">menu5</a></li>
    </ul>
  </nav>
  <div class="wrapper">
    <h1>sample</h1>
    <section>
      <img src="http://lorempixel.com/400/200" alt="">
      <h2 id="sec1">section1</h2>
      <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sunt autem doloremque, ab iusto quos consequuntur architecto molestiae quibusdam rerum sit eum sint itaque fuga exercitationem, aspernatur velit. Veritatis, cum, deserunt.</p>
      <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sunt autem doloremque, ab iusto quos consequuntur architecto molestiae quibusdam rerum sit eum sint itaque fuga exercitationem, aspernatur velit. Veritatis, cum, deserunt.</p>
      <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sunt autem doloremque, ab iusto quos consequuntur architecto molestiae quibusdam rerum sit eum sint itaque fuga exercitationem, aspernatur velit. Veritatis, cum, deserunt.</p>
      <h2 id="sec2">section2</h2>
      <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sunt autem doloremque, ab iusto quos consequuntur architecto molestiae quibusdam rerum sit eum sint itaque fuga exercitationem, aspernatur velit. Veritatis, cum, deserunt.</p>
      <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sunt autem doloremque, ab iusto quos consequuntur architecto molestiae quibusdam rerum sit eum sint itaque fuga exercitationem, aspernatur velit. Veritatis, cum, deserunt.</p>
      <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sunt autem doloremque, ab iusto quos consequuntur architecto molestiae quibusdam rerum sit eum sint itaque fuga exercitationem, aspernatur velit. Veritatis, cum, deserunt.</p>
      <h2 id="sec3">section3</h2>
      <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sunt autem doloremque, ab iusto quos consequuntur architecto molestiae quibusdam rerum sit eum sint itaque fuga exercitationem, aspernatur velit. Veritatis, cum, deserunt.</p>
      <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sunt autem doloremque, ab iusto quos consequuntur architecto molestiae quibusdam rerum sit eum sint itaque fuga exercitationem, aspernatur velit. Veritatis, cum, deserunt.</p>
      <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sunt autem doloremque, ab iusto quos consequuntur architecto molestiae quibusdam rerum sit eum sint itaque fuga exercitationem, aspernatur velit. Veritatis, cum, deserunt.</p>
      <h2 id="sec4">section4</h2>
      <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sunt autem doloremque, ab iusto quos consequuntur architecto molestiae quibusdam rerum sit eum sint itaque fuga exercitationem, aspernatur velit. Veritatis, cum, deserunt.</p>
      <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sunt autem doloremque, ab iusto quos consequuntur architecto molestiae quibusdam rerum sit eum sint itaque fuga exercitationem, aspernatur velit. Veritatis, cum, deserunt.</p>
      <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sunt autem doloremque, ab iusto quos consequuntur architecto molestiae quibusdam rerum sit eum sint itaque fuga exercitationem, aspernatur velit. Veritatis, cum, deserunt.</p>
      <h2 id="sec5">section5</h2>
      <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sunt autem doloremque, ab iusto quos consequuntur architecto molestiae quibusdam rerum sit eum sint itaque fuga exercitationem, aspernatur velit. Veritatis, cum, deserunt.</p>
      <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sunt autem doloremque, ab iusto quos consequuntur architecto molestiae quibusdam rerum sit eum sint itaque fuga exercitationem, aspernatur velit. Veritatis, cum, deserunt.</p>
      <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sunt autem doloremque, ab iusto quos consequuntur architecto molestiae quibusdam rerum sit eum sint itaque fuga exercitationem, aspernatur velit. Veritatis, cum, deserunt.</p>
    </section>
  </div>
  <footer>Footer</footer>

CSSの大きな変更点はメディアクエリを使用してPCサイズのときの表示とスマートフォンサイズのときの表示を切り分けました。切り替えポイントは736pxです。

PCサイズのときの表示は横並びで画面上部の中央に整列するようにCSSでレイアウトしています。ナビゲーションを中央揃えにするポイントは「.gnav」のulにpositionを指定して「left: 50%」とします。このままでは「.gnav」の左端が中央に来ますので、「.gnav li 」を「left:-50%」にして中身ののliを左側にずらせます。
ナビゲーションの中央揃えの方法はこちらを参考にしてください。

スマートフォンサイズでのドロワーメニューでは「.gnav」に対してtransitionを設定してメニュー開閉のアニメーションを行いますが、メディアクエリのポイント切り替えで不具合が生じるためにjQueryで動的に作成することにしてCSSからは削除しています。

154行目の!importantはヘッダー部分の透明化に伴いハンバーガーボタンの色変更をjQueryで行いますが、CSS優先度がjQueryで変更したCSSの方が優先度が高くなるために真ん中の線が消えない不具合がでます。jQueryのコードで対応することも可能ですが、ここは禁じ手の!importantで優先度を上げています。

CSSコード

  body,
  ul,
  li {
    margin: 0;
    padding: 0;
  }

  .wrapper {
    box-sizing: border-box;
    padding: 0 15px;
    margin: 55px 0;
  }

  img {
    max-width: 100%;
    height: auto;
  }

  header {
    box-sizing: border-box;
    background: #333;
    color: #ccc;
    padding: 1rem;
    position: fixed;
    width: 100%;
    top: 0;
    z-index: 98;
  }

  footer {
    background: #ccc;
    padding: 1rem;
    position: fixed;
    bottom: 0;
    width: 100%;
    z-index: 98;
  }
    /*nabi開閉部分*/

    .gnav li {
      border-bottom: 1px solid #333;
    }

    .gnav li a {
      display: block;
      text-decoration: none;
      /* (44-16)/2=14px */
      padding: .875rem 1rem;
    }

    .gnav {
      list-style-type: none;
      background: #eee;
      display: block;
      width: 70%;
      overflow-x: hidden;
      overflow-y: auto;
      position: fixed;
      left: 0;
      top: 55px;
      z-index: 99;
      visibility: hidden;
      -webkit-transform: translateX(-100%);
      -moz-transform: translateX(-100%);
      -ms-transform: translateX(-100%);
      -o-transform: translateX(-100%);
      transform: translateX(-100%);
      webkit-transform-style: preserve-3d;
      -moz-transform-style: preserve-3d;
      -ms-transform-style: preserve-3d;
      -o-transform-style: preserve-3d;
      transform-style: preserve-3d;
    }

    .gnav.on {
      visibility: visible;
      -webkit-transform: translateX(0px);
      -moz-transform: translateX(0px);
      -ms-transform: translateX(0px);
      -o-transform: translateX(0px);
      transform: translateX(0px);
    }

    .modal {
      background-color: rgba(255, 255, 255, .5);
      width: 100%;
      height: 100%;
      left: 0;
      opacity: .1;
      position: fixed;
      top: 0;
      z-index: 97;
      visibility: hidden;
      webkit-transition: visibility 0 linear .4s, opacity .4s;
      -moz-transition: visibility 0 linear .4s, opacity .4s;
      transition: visibility 0 linear .4s, opacity .4s;
      webkit-transform: translateZ(0);
      -moz-transform: translateZ(0);
      -ms-transform: translateZ(0);
      -o-transform: translateZ(0);
      transform: translateZ(0);
    }

    .modal.on {
      opacity: 1;
      webkit-transition-delay: 0;
      -moz-transition-delay: 0;
      transition-delay: 0;
      visibility: visible;
    }
    /*ハンバーガーボタン*/

    .icon-animation {
      width: 44px;
      height: 44px;
      display: block;
      cursor: pointer;
      position: fixed;
      right: .5rem;
      top: .5rem;
      text-align: center;
      z-index: 99;
    }

    .icon-animation span {
      width: 44px;
      height: 1px;
      display: block;
      background: #fff;
      position: absolute;
      left: 50%;
      top: 50%;
      margin-left: -22px;
      -webkit-transition: all 0.3s;
      transition: all 0.3s;
      -webkit-transform: rotate(0deg);
      -ms-transform: rotate(0deg);
      transform: rotate(0deg);
    }

    .icon-animation .top {
      -webkit-transform: translateY(-13px);
      -ms-transform: translateY(-13px);
      transform: translateY(-13px);
    }

    .icon-animation .bottom {
      -webkit-transform: translateY(13px);
      -ms-transform: translateY(13px);
      transform: translateY(13px);
    }

    .is-open .middle {
      background: rgba(51, 51, 51, 0)!important;
    }

    .is-open .top {
      -webkit-transform: rotate(-45deg) translateY(0px);
      -ms-transform: rotate(-45deg) translateY(0px);
      transform: rotate(-45deg) translateY(0px);
    }

    .is-open .bottom {
      -webkit-transform: rotate(45deg) translateY(0px);
      -ms-transform: rotate(45deg) translateY(0px);
      transform: rotate(45deg) translateY(0px);
    }

    @media (min-width:736px) {
      header {
        display: block;
      }
      .global {
        width: 100%;
        position: relative;
        /*ナビ中央そろえ*/
        overflow: hidden;
        /*ナビ中央そろえ*/
      }
      .gnav-btn {
        display: none;
      }
      .gnav {
        list-style-type: none;
        background-color: inherit;
        display: block;
        overflow: visible;
        /*ナビ表示*/
        position: fixed;
        /*ナビ中央そろえ*/
        width: auto;
        left: 50%;
        /*ナビ中央そろえ*/
        top: 0;
        z-index: 99;
        visibility: visible;
        -webkit-transform: translateX(0px);
        -moz-transform: translateX(0px);
        -ms-transform: translateX(0px);
        -o-transform: translateX(0px);
        transform: translateX(0px);
      }
      .gnav li {
        border-bottom: none;
        width: auto;
        position: relative;
        /*ナビ中央そろえ*/
        left: -50%;
        /*ナビ中央そろえ*/
        float: left;
        /*ナビ中央そろえ*/
      }
      .gnav li a {
        display: block;
        text-decoration: none;
        padding: .875rem 0.5rem;
        color: #fff;
      }
    }

jQueryのポイントはCSS3のアニメーションつまりtransitionの設定を動的に作成するようにしたことです。ハンバーガーボタンをクリックすると「is-open」クラスの着脱を行い、「.gnav」にtransitionの設定をします。こうすることでメディアクエリからのトリガーの影響を受けなくすることができます。

また、ヘッダー部分が下スクロールしたときに邪魔になるため100pxスクロールしたらヘッダーを消すようにしました。ただし、ナビゲーションを呼び出すためのハンバーガーボタンだけは表示させています。
下のjQueryコード27行目〜49行目までがその処理になります。27行目の$(window).on(‘load resize’,function(){…と書くことでページが表示された最初の段階と、リサイズされたときのそれぞれのイベントに対して同一の処理を書いています。この用法がon()を使うメリットでもあります。

on()では、複数のイベントに対して一括して、同じイベントハンドラを定義することができます。2つのイベントを半角スペースで区切って指定します。また2つのイベントは”でくくります。
on(‘load resize’,function() {
処理内容
}

setTimeoutを入れているのはresizeイベントは1pxでも画面サイズが変わると発生してしまい、画面サイズを変更する間中イベントが断続的に起こるのを防ぐために使用しています。

45行目以降はナビゲーションのメニューをクリックしたらいきなり対応するセクションに飛ぶのではなく、スルスルっとアニメーションしながら動く動作をつけています。

jQueryでは属性セレクタの一種で[href*=#]を使用しています。属性セレクタはjQueryで便利に活用できますので使い方をしっかり覚えておくとよいでしょう。[href*=#]はhref属性の値が#と部分一致した場合にマッチします。

48行目の-0は、一番上に戻る際に0pxだけ間をあけるためです。ヘッダー部分の領域だけ下にコンテンツをさげるために使用しますが、今回はヘッダーを消していますので0pxにしていますが、デザインに応じて数値を変更してください。

「scrollTop」はメソッドではありません。jQueryが用意したCSS用のプロパティで、 animate() の中で使用します。
scrollTopの使い方はこちらを参照してください。

jQueryコード

    $(function() {
      $('body').append('<div class="modal"></div>');
      var gnav = $('.gnav');
      var modal = $('.modal');
      var gnavbtn = $('.gnav-btn');
      $(window).resize(function() {
        gnav.css('transition', 'none');
        modal.removeClass("on");
        gnav.removeClass("on");
        gnavbtn.removeClass('is-open');
      });
      gnavbtn.on('click', function() {
        $(this).toggleClass('is-open');
        gnav.css('transition', '.5s .1s cubic-bezier(0, 0, .2, 0)').toggleClass('on');
        modal.toggleClass('on');
      });
      $('.gnav li,.modal').on('click', function() {
        modal.removeClass("on");
        gnav.removeClass("on");
        gnavbtn.removeClass('is-open');
      });
      $(window).on('load resize',function() {
        var timer = false;
        if (timer !== false) {
          clearTimeout(timer);
        }
        timer = setTimeout(function() {
          if (window.matchMedia("(max-width:737px)").matches) {
            //737px以下の場合
            $(window).on('scroll', function() {
              if ($(this).scrollTop() < 100) {
                $('header').fadeIn();
                $('.icon-animation span').css('background-color', '#fff');
              } else {
                $('header').fadeOut();
                $('.icon-animation span').css('background-color', '#000');
              }
            });
          } else if (window.matchMedia("(min-width:736px)").matches){
            $(window).off('scroll');
            $('header').show(); $('.icon-animation span').css('background-color', '#fff');
          }
        }, 200);
      })
        /*リンクの動きをゆっくり移動にするスクリプト*/
     $("a&#91;href*=#&#93;").click(function() {
        $('html,body').animate({
          scrollTop: $($(this).attr("href")).offset().top - 0
        }, 'slow', 'swing');
        /*クリックしたらナビを閉じる動作*/
        modal.removeClass("on");
        gnav.removeClass("on");
        gnavbtn.removeClass('is-open');
        //リンクを無効
        return false;
      })
    });
&#91;/js&#93;

参考:<a href="https://itstudio.co/sample/transition3/sample2.html" target="_blank">CSS3 transitionを使用しないjQueryだけのドロワーメニューのサンプル</a>

<h2>CSS3だけでドロワーメニュー作成(必要に応じてJavaScript併用)</h2>

この方法はチェックボックスのチェックの有無でトリガーを発生させる方法です。
なんだか怪しげな方法ですが、実はAppleの公式サイトのモバイルサイズのナビゲーションはこの方法が採られています。(2015/10/28現在)

今回のサンプルはjQueryを使用せずにCSS3だけでアニメーションしたものです。そのため機能的には上の改良版のサンプルよりも劣っています。
<a href="https://itstudio.co/sample/transition3/sample4.html" target="_blank">CSS3だけでドロワーメニューサンプル</a>

トリガーにチェックボックスの「:checked」を使っています。疑似クラス「:checked」の有無でトグルスイッチ化しています。
疑似クラス「:checked」で動作する(レイアウトが決まる)ためチェックボックスを置く位置が重要になります。
このサンプルではnavの直下に配置して、ハンバーガーボタンの"gnav-btn"やナビゲーション可動部の"gnav"と兄弟要素になるように配置しました。
また、ナビゲーションが開いた時のモーダルの役目をする"modal"とも兄弟要素の状態にしています。

onClick="navClick()"はJavaScriptです。メニューをクリックしたらナビゲーションを閉じる関数を呼び出しているものです。
これは必要に応じて使用しますが、スクリプト皆無にこだわるよりも利便性を考えるべきだと思います。
ちなみに、Appleのナビゲーションの仕組みもトリガーにチェックボックスの「:checked」を使っていますが、随所にJavaScriptが併用されています。

HTMLコード


ナビゲーションの開閉は
:checkedをトリガーにした動作をするために「.check:checked ~.gnav」のように記述をして、チェックが入ったチェックボックスの後にある兄弟要素の「.gnav」というセレクタを書いています。この書き方がポイントです。

<div class="lecture">
E ~ F
一般兄弟結合子で、E 要素のあとに現れる F 要素という意味
</div>

ナビゲーション開閉のアニメーションはtransitionを使用しますが、「 .gnav」に設定するとブラウザの幅を広げたり縮めたりしたときにメディアクエリのトリガーが発せられますので、変なアニメーションがでます。そのためこのサンプルでは「.check:checked ~ .gnav」にtransitionを設定しました。
トリガーになる要素を含めたセレクタにtransitionを設定すると片側通行のアニメーションになります。従ってナビゲーションが開くときはアニメーションしますが、閉じるときはアニメーションしません。目的のリンク先をタップした場合もうナビゲーションには用がありませんので即座に消えてくれても問題はないかもしれません。ただし、問題点はタップしてもそもそもナビゲーションが残ってしまい、Xをタップするか背景をタップしないと消えてくれないことです。
そこで、このサンプルではJavaScriptを使用してナビゲーションのメニューをクリックしたらナビゲーションを閉じるように設定しています。

ハンバーガーボタンのアニメーションは
「.check:checked + .gnav-btn .middle 」のように隣接兄弟結合子を使用しています。この場合はチェックの入ったチェックボックスのすぐ後にある兄弟要素の「.gnav-btn」の中に入っている「.middle」という意味合いになります。

<div class="lecture">
E + F
隣接兄弟結合子。E 要素の直後に現れる F 要素という意味
</div>

要領がつかめれば以外と簡単な仕組みです。
CSSコード


ナビゲーションのメニューをクリックしたらナビゲーションを閉じる仕組みは、クリックしたら#checkのチェックボックスのチェックをJavaScriptで外しています。

JavaScriptのコード

  function navClick(){
      document.getElementById('check').checked = false;
    }
タイトルとURLをコピーしました