解 説

スマートフォン向けのサイト作成の時に定番的に使われている開閉式のナビゲーションの作成方法です。今回は縦方向の開閉と横方向の開閉(ドロワーメニュー)の基本的な仕組みの解説です。
sp1
jQueryとCSS3を使用した作成方法です。通常jQueryのアニメーションはCPUで処理されますが、CSS3を有効活用するとGPUが使用されてより滑らかな動きを実現できます。

縦方向の開閉メニュー

ハンバーガーボタンをタップするとアコーデオン状に開くナビゲーションです。
ハンバーガーボタンはタップするとX印に変わります。X印をタップするとナビゲーションを閉じることができます。
比較的多く見るタイプのものです。

HTMLのポイントはハンバーガーボタン部分とナビゲーション部分の構造です。
ハンバーガーボタンはdivとspanで作成します。
ナビゲーションはulとliでできた一般的なものです。

縦方向の開閉メニューのサンプル

HTMLコード

  <div class="nav-wrap">
    <header>Header
      <div class="gnav-btn">
        <div class="icon-animation">
          <span class="top"></span>
          <span class="middle"></span>
          <span class="bottom"></span>
        </div>
      </div>
    </header>
    <nav>
      <ul class="gnav">
        <li><a href="">menu1</a></li>
        <li><a href="">menu2</a></li>
        <li><a href="">menu3</a></li>
        <li><a href="">menu4</a></li>
        <li><a href="">menu5</a></li>
      </ul>
    </nav>
  </div>
  <div class="wrapper">
    <h1>sample</h1>
    <section>
      <img src="http://lorempixel.com/400/200" alt="">
      <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コードはレイアウト部分はシンプルに書いています。全体のレイアウトでは、コンテンツ部分を囲む「.wrapper」には「box-sizing: border-box」を設定してwidthを100%に設定してもパディング部分が表示領域からはみ出さないようにしています。「box-sizing: border-box」はボックスモデルの計算方法を変えるものですが、スマートフォンサイト作成時には重宝します。
imgには「max-width: 100%;」を設定して画像の縮小に対応しています。

ナビゲーション部分はヘッダーと共に上部に固定するために、「.nav-wrap」でヘッダーとナビゲーションをグループ化してfixedで固定しています。この辺のレイアウトは自分のサイトのデザインに応じて対応してください。今回のサンプルでは「.nav-wrap」を固定化したために「.wrapper」が「.nav-wrap」の裏側に潜り込みます。そのため「.wrapper」に margin-topでマージンを空けています。今回はpx指定ですが、基準のフォントサイズなどしっかりと設計している場合はremで指定すると良いでしょう。

ナビゲーションの母体「.gnav」は初期状態をjQueryで「display: none」で非表示にしています。
「.gnav」のz-index:99としていますが、この値は作品に応じて変更の必要があります。コンテンツ内に複数のz-indexを使用していたならそれに応じてz-index値の数字を大きくする必要があります。けれどもやみくもに9999などの数値を使うのではなく、きちんと理解して数値を算出すべきです。z-indexについてはこちらを参考にしてください。

ハンバーガーボタンの三本線はspan要素のheightを1pxにして背景色で線を引いたように見せています。「.icon-animation span 」で整形しています。これだけのCSSだったら三本線は全部真ん中の線に重なり、1本線の状態になりますが、「.icon-animation .top」と「.icon-animation .bottom」でそれぞれtranslateY(13px)で13pxだけ上下に移動しています。これは0.3秒の速さでアニメーションしています。

jQueryでこのボタンをクリックしたことを認識したなら「.is-open」のクラス名が付加されます。jQueryでクラス名が付くことがトリガーになります。クラス付与でtransitionアニメーションが動き出すためには「.icon-animation span 」の中にtransitionアニメーションの設定を入れます。

クリック後のハンバーガーボタンの三本線は「.is-open .top」と「.is-open .bottom」と「.is-open .middle」に記述されています。
「.is-open .top」と「.is-open .bottom」は「 rotate(-45deg)」で横棒をXになるように回転させています。「.is-open .middle」では線の色(spanの背景色)をヘッダーの背景色と同じ色にしてさらに透明化することで横棒を消しています。

ハンバーガーボタンの作成はLIGのブログを参考にしています。他にも様々な動きの例がありますので参考にしてください。

CSSコード

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

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

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

    header {
      background: #333;
      color: #ccc;
      padding: 1rem;
    }

    footer {
      background: #ccc;
      padding: 1rem;
    }
    /*nabi開閉部分*/
    .nav-wrap{
        box-sizing: border-box;
        position: fixed;
        top: 0;
        width: 100%;
    }
    .gnav {
      list-style-type: none;
      background: #eee;
      width: 100%;
      /* z-indexは.wrapper部分でpositionを使用した場合は適切な数字(一番大きい値)に変更*/
      z-index: 99;
    }

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

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

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

    .icon-animation span {
      width: 39px;
      height: 1px;
      display: block;
      background: #fff;
      position: absolute;
      left: 50%;
      top: 50%;
      margin-left: -25px;
      -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);
    }

    .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);
    }

jQueryコードでは、クリックイベントに対してslideToggle()メソッドを使用してナビゲーションの母体になるul要素を表示したり、非表示にしたりします。またクリックイベントではその他にクラス名「is-open」を付けたり、外したりしてハンバーガーボタンの形態をCSSで変化させています。

slideToggle()メソッドは要素が表示されている時は「slideUp」で隠し(display:none)、隠れている時は「slideDown」で表示します(display:block)

clickイベントはon()を使用した書き方の方がclick()よりも用途が広がります。この書き方をしっかり覚えましょう。
jQueryコード

$(function() {
      var gnav = $('.gnav');
      gnav.css('display','none');
      $('.gnav-btn').on('click', function() {
        gnav.slideToggle(500);
        if ($(this).hasClass('is-open')) {
          $(this).removeClass('is-open');
        } else {
          $(this).addClass('is-open');
        }
      });
    });

ドロワーメニュー(横開きメニュー)

ハンバーガーボタンをタップすると横からスルスルと出てくるナビゲーションのことをドロワーメニュー(引き出し)と呼びます。

ドロワーメニュー左開閉式サンプル
ドロワーメニューヘッダー固定サンプル
ドロワーメニュー右開閉式サンプル

HTMLコードは縦開きメニューとあまり変わりません。ヘッダーとナビをグループ化するdivは必要なくなったため無くしています。
bodyの終了タグの前には「class=”modal”」の空のdivを置いています。これはナビが表示された時に全体を覆って霞ませるためのレイヤーの役目をします。modalのdivは空のためHTMLに記述したくない場合はjQueryで動的に作成すれば良いでしょう。今回は動作がわかりやすいようにHTMLに記述しています。

HTMLコード

    <header>Header
      <div class="gnav-btn">
        <div class="icon-animation">
          <span class="top"></span>
          <span class="middle"></span>
          <span class="bottom"></span>
        </div>
      </div>
    </header>
    <nav class="global">
      <ul class="gnav">
        <li><a href="">menu1</a></li>
        <li><a href="">menu2</a></li>
        <li><a href="">menu3</a></li>
        <li><a href="">menu4</a></li>
        <li><a href="">menu5</a></li>
      </ul>
    </nav>
    <div class="wrapper">
      <h1>sample</h1>
      <section>
        <img src="http://lorempixel.com/400/200" alt="">
        <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>
    <div class="modal"></div>

このサンプルの開閉のポイントは「.gnav」です。これはナビの母体です。背景色を指定して、fixedの指定をして固定させます。座標は左上です。
次にtranslateXを-181pxとして左に移動し、更にvisibility: hiddenで消して隠します。jQueryで「.gnav」に更に「.on」が付けられると(クラス名を2つ指定)それがトリガーになってtransitionアニメーションを実行します。アニメーションの内容はナビゲーションの位置が-100%から0pxに変更されて、更にvisibility: visibleに変化して表示されることになります。

transitionの値でcubic-bezier()はベゼー曲線を描くための座標を指定するものです。第1引数は起点のハンドルのx座標第2引数は起点のハンドルのy座標、第3引数は終点のハンドルのx座標、第4引数は終点のハンドルのy座標です。
bezier

bezier2

また、ハンバーガーボタンがタップされた時に 「.modal」に「.on」(クラス名を2つ指定)が付加されます。これがトリガーになって opacity: 1となりモーダルレイヤーが画面いっぱいに広がった状態になります。このモーダルレイヤー上またはハンバーガーボタンのXがタップされると「.on」は取り去られてモーダルレイヤーは消えます。モーダルレイヤーをタップするとナビゲーションを隠す役目も持っています。

CSSコード

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

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

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

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

    footer {
      background: #ccc;
      padding: 1rem;
    }
    /*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;
      -webkit-transition: .5s .1s cubic-bezier(0, 0, .2, 0);
      -moz-transition: .5s .1s cubic-bezier(0, 0, .2, 0);
      transition: .5s .1s cubic-bezier(0, 0, .2, 0);
    }

    .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: 98;
      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;
      float: right;
      position: absolute;
      right: .5rem;
      text-align: center;
      top: .5rem;
    }

    .icon-animation span {
      width: 39px;
      height: 1px;
      display: block;
      background: #fff;
      position: absolute;
      left: 50%;
      top: 50%;
      margin-left: -25px;
      -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);
    }

    .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);
    }

このサンプルではjQueryではクラス名を付加したり外したりするでけのものです。
「.gnav」と「.modal」に「.on」を付けたり「.gnav-btn」に「.is-open」を付けたりして表示と非表示を行っています。
「$(‘.gnav’)」のようなjQueryオブジェクトを頻繁に作成するとPCに負担がかかります。できるだけ変数を使用するなどすると良いでしょう。

jQueryコード

  $(function() {
      var gnav = $('.gnav');
      var modal = $('.modal');
      var gnavbtn = $('.gnav-btn');
      gnavbtn.on('click', function() {
        if ($(this).hasClass('is-open')) {
          $(this).removeClass('is-open');
          gnav.removeClass('on');
          modal.removeClass('on');
        } else {
          $(this).addClass('is-open');
          gnav.addClass('on');
          modal.addClass('on');
        }
      });
      modal.on("click", function() {
        $(this).removeClass("on");
        gnav.removeClass("on");
        gnavbtn.removeClass('is-open');
      });
    });

参考:jQueryで動的にdivを作成するコード

$('body').append('<div class="modal"></div>');

今回紹介したサンプルは開閉メニュー動作の仕組みを知るためのものです。レスポンシブサイトなどに応用すると不具合が出る場合があります。
近日中に考えられる不具合とその対策を公開する予定です。