目次

初心者向け!ハンバーガーメニューの作り方入門編

簡単なハンバーガーメニューの作り方を初心者向けにまとめました。HTMLの構造や制御すべきJavaScript、基本的なCSSで制作の流れを解説しています。


PREPARATION


想定する読者と前提条件

基本的なパソコン操作ができること。HTML及びCSS,JavaScriptの基礎程度の知識が必要です。
今回の記事は、様々な情報への入り口になるように少しずつ各情報を紹介しております。 必ずしも今回ご紹介するものが正解とは限りません。使用される用途に合わせてお読み下さい。

初版完了


START


ハンバーガーメニューとは?

メニューにアクセスするための入り口に使用される三本線のアイコンのことをハンバーガーメニューと呼んでいます。 もともとは表示領域の小さい端末で利用されていましたが、現在では様々な場面で見ることが多くなってきましたね。 今回は三本線のハンバーガーメニューの作成方法を詳しく見ていきます。

どうやって作るの?

まずは、あの三本線がどのように作られているのか調査してみましょう。 各サイトでも表示方法が異なりますので、詳しく見ていきます。

Apple

span要素で構成され、CSSで横棒を作成しています。Appeは2本線で構成されているのが特徴です。

<input type="checkbox" id="ac-gn-menustate">
<label for="ac-gn-menustate" aria-hidden="true">
    <span>
        <span></span><!-- 上の線 -->
    </span>
    <span>
        <span></span><!-- 下の線 -->
    </span>
</label>

Sony

img要素で画像を使用して三本線が構成されています。

<button>
    <img src="/header-footer/header/images/toggle_open.png" alt="メニューを開く">
</button>

Google

svg要素で三本線を構成しています。

<div jsname="hyP9Qc"  aria-label="メインメニュー" aria-controls="UCqTdf" aria-expanded="false" aria-haspopup="true" role="button" tabindex="0" jsaction="x097hd" >
<svg style="fill:#70757a;width:24px;height:24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"></path><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"></path></svg>
</div>

Microsoft

CSSの疑似要素を使用し、アイコンフォントで三本線を構成しています。

<button type="button"  aria-label="すべての Microsoft を展開して、マイクロソフト製品とサービスの一覧を表示する" initialstate-label="すべての Microsoft を展開して、マイクロソフト製品とサービスの一覧を表示する" togglestate-label="Close All Microsoft list" aria-expanded="false"  tabindex="2"></button>


QUESTION


  • 作り方は大きく分けて4種類あるわ。

    (1)CSSで作る。
    (2)SVGで作る。
    (3)画像で作る。
    (4)アイコンフォントで作る。

    メニューの高さはだいたい、48px~70px程度のところが多いわね。一般的にタッチ可能な要素は、44px × 44px以上を最低確保する必要があるわ。

    avatar
  • avatar
    どれを採用すればいいんだい?

    自分が得意なスキルで選んでしまってもいいのかい?

  • そうねぇ~?

    ただ、一つ言えることは、画像やアイコンフォントは無駄なリソースを消費することになる。 もしハンバーガーメニューのためだけに、画像やアイコンフォントを使用するんだとしたら、無駄でしか無いわ。
    正直、ハンバーガーの形を作るという目的があるのであれば、spanやdivなんかで作らずSVGが最適解だと思う。 疑似要素も利用する人が多いけど、なぜ疑似要素なのかしら?
    だからここでは、SVGのハンバーガーメニューを採用するわ。

    avatar

まずはHTMLの構造を考えてみよう

ここから、少しずつコードを書きながらハンバーガーメニューとメニューとして機能させるためのナビゲーションバー、そしてハンバーガーメニューをタップしたい際に表示されるメニューの構成を見ていきましょう。

まずは3本線を作ろう

先程も書いたとおり、今回はインラインSVGで三本線を作成します。インラインSVGによる三本線のコードは以下のとおりです。 横幅23px 縦14px(全体で15px)でレイアウトしています。線の色やsvgのサイズは、CSSでレイアウトしていきます。 線をそれぞれ、top,middle,bottomというclassで設定しています。
また、svgのtitle要素には、ハンバーガーボタンの説明を入れるようにします。一般的にtitle要素は描画されませんが、音声支援技術などでは読み上げることができます。

<svg xmlns="http://www.w3.org/2000/svg" 
viewBox="0 0 23 15">
<title id="global-menu-title">メインメニューを開く</title>
<g><line class="top" x1="0" y1="0.5" x2="23" y2="0.5"/>
<line class="middle" x1="0" y1="7.5" x2="23" y2="7.5"/>
<line class="bottom" x1="0" y1="14.5" x2="23" y2="14.5"/></g>
</svg>

SVGハンバーガー(1)

プレビュー

ボタンとして機能させよう!

次にこの三本線に”メニューを開く”という役割を与えます。ハンバーガーメニューの目的は、隠されたサブメニューを開くというものです。 一般的にユーザーと対話するインタラクティブ要素であるbutton要素は、フォームの送信、ダイアログやメニューのオープン、アクションのキャンセル、新しいレコードの挿入や情報の表示などのアクションを実行するために使用するウィジェットです。 ここでは、メニューを開くという目的のためにボタン要素を使用してハンバーガーメニューを作成していきます。

<button type="button">
<svg xmlns="http://www.w3.org/2000/svg" 
viewBox="0 0 23 15">
<title id="global-menu-title">メインメニューを開く</title>
<g><line class="top" x1="0" y1="0.5" x2="23" y2="0.5"/>
<line class="middle" x1="0" y1="7.5" x2="23" y2="7.5"/>
<line class="bottom" x1="0" y1="14.5" x2="23" y2="14.5"/></g>
</svg>
</button>

次にボタン要素をタップ(クリック)可能な要素として配置するため、今回は49px × 49pxの大きさでボタン要素を作成し、 下記CSSを用いて、一般的なブラウザで表示されるボタンレイアウトをリセットします。

/* ボタンデザインのリセット */
button {margin:0; padding:0; border-radius:0;background:transparent; border:none;outline:none; appearance:none; }

SVGハンバーガー(2)

プレビュー

ナビゲーションメニューに組み込もう

HTMLのnav要素は、現在のページから別のページへのナビゲーションリンクを提供するために使用されます。ナビゲーションの各項目はul要素で作成されます。 先程作成したボタン要素をナビゲーションの一部として組み込んでみましょう。(少しずつコードが増えていきますので、じっくり見て下さい。)
また、レイアウトしやすいように各要素にclass名、id名を設定しています。

<nav id="global-menu">
  <ul class="mobile" id="mobile-menubar" >
    <li>
        <button type="button" id="global-menu-button">
        <svg 
        id="global-menu-img"
        xmlns="http://www.w3.org/2000/svg" 
        viewBox="0 0 23 15">
        <g><line class="top" x1="0" y1="0.5" x2="23" y2="0.5"/>
        <line class="middle" x1="0" y1="7.5" x2="23" y2="7.5"/>
        <line class="bottom" x1="0" y1="14.5" x2="23" y2="14.5"/></g>
        </svg>
        </button>
    </li>
    <li class="logo" ><a href="#">タイトル</a></li>
    </ul>
</nav>

オーバーレイメニューを作ろう

次に、ハンバーガーメニューをタップした際に表示するメニューを作成します。 やることは、単純でナビゲーション内にハンバーガーメニューをタップした際に表示したい内容(サブメニュー)を追加していきます。

<nav id="global-menu">
    <ul class="mobile" id="mobile-menubar" >
      <li>
          <button type="button" id="global-menu-button">
          <svg 
          id="global-menu-img"
          xmlns="http://www.w3.org/2000/svg" 
          viewBox="0 0 23 15">
          <g><line class="top" x1="0" y1="0.5" x2="23" y2="0.5"/>
          <line class="middle" x1="0" y1="7.5" x2="23" y2="7.5"/>
          <line class="bottom" x1="0" y1="14.5" x2="23" y2="14.5"/></g>
          </svg>
          </button>
      </li>
    <li class="logo" ><a href="#">タイトル</a></li>
    </ul>
    
    <!-- ハンバーガーメニューをタップした場合に表示する内容 -->
    <div id="global-menu-overlay" >
      <ul>
          <li class="link"><a href="#">SP MENU1</a></li>
          <li class="link"><a href="#">SP MENU2</a></li>
          <li class="link"><a href="#">SP MENU3</a></li>
      </ul>
    </div>
</nav>

デスクトップメニューを作ろう

パソコン画面で閲覧した場合と、スマートフォン等で閲覧した場合のナビゲーションメニューの切り替えを行う場合、 デスクトップ用のメニューを追加する必要があります。nav要素内にPC向けメニューを記載したul要素をさらに追加します。 (共通のメニューを使用する場合には不要です。)

  <nav id="global-menu">
      <!-- PC向けメニュー -->
      <ul class="desktop" id="desktop-menubar">
        <li class="logo"><a href="#">タイトル</a></li>
        <li class="link"><a href="#">PC MENU1</a></li>
        <li class="link"><a href="#">PC MENU2</a></li>
        <li class="link"><a href="#">PC MENU3</a></li>
      </ul>
      
      <!-- SP向けメニュー -->
      <ul class="mobile" id="mobile-menubar" >
        <li>
          <button type="button" id="global-menu-button">
           <svg 
            id="global-menu-img"
            xmlns="http://www.w3.org/2000/svg" 
            viewBox="0 0 23 15">
            <g><line class="top" x1="0" y1="0.5" x2="23" y2="0.5"/>
            <line class="middle" x1="0" y1="7.5" x2="23" y2="7.5"/>
            <line class="bottom" x1="0" y1="14.5" x2="23" y2="14.5"/></g>
            </svg>
          </button>
        </li>
        <li class="logo" ><a href="#">タイトル</a></li>
      </ul>
      
      <!-- ハンバーガーメニューをタップした場合に表示する内容 -->
      <div id="global-menu-overlay" >
        <ul>
          <li class="link"><a href="#">SP MENU1</a></li>
          <li class="link"><a href="#">SP MENU2</a></li>
          <li class="link"><a href="#">SP MENU3</a></li>
        </ul>
      </div>
    </nav>

WAI-ARIAを取り入れよう

と、その前にウェブアクセシビリティと言う言葉について理解する必要があります。 ウェブアクセシビリティとは、ウェブサイトの情報や機能の利用しやすさを意味します。

健常者も身体的ハンディキャップを持った人も、 パソコンやモバイル端末等様々な環境の人にも利用しやすくすることです。

WAI-ARIA(Accessible Rich Internet Applications)とは、 ブラウザや支援技術が認識できるさらなる意味をHTMLに追加することによって閲覧のしやすさを向上させ、ウェブサイト利用者の理解を助ける為の技術となります。

アクセシビリティについて詳しく知りたい場合は、下記サイトをご覧ください。

ウェブアクセシビリティ基盤委員会公式サイト

WAI-ARIA日本語訳

WAI-ARIAを使ってみよう

WAI-ARIAの基本は、HTML要素に適用できる追加の意味を定義しています。

この仕様では、主に次の3つの機能があります。 ロール(Role)属性は意味を、プロパティ(Property)属性は性質を、 ステート(State)属性は状態を定義しています。

これらを使いHTMLに意味付けを行うことができます。

W3Cでは、WAI-ARIAの実装例をWAI-ARIA Authoring Practices 1.1で 公開しております。今回は、メニューボタンの項目について取り上げます。

WAI-ARIA menu-button-links

ロール、プロパティ、状態

早速ナビゲーションで利用されるWAI-ARIAの各項目を詳しく見ていきましょう。

メニューボタン関連(ハンバーガーメニュー)

aria-haspopup="true"

[対象:button要素]ボタン要素が他のメニューを開くことを表します。 aria-haspopupの状態は、trueまたはmenuが最適です。他の値としては、"false"、"listbox"、"tree"、"grid"、"dialog"があります。

aria-controls="IDREF"

[対象:button要素]メニュー ボタンによって制御されるメニュー要素(サブメニュー)を参照(id名を指定)します。 今回の場合、aria-controls="global-menu-overlay"と指定します。

aria-expanded="true"

[対象:button要素]メニュー ボタンによって制御されるメニュー要素が展開しているのか、折りたたまれているかを表します。
true:展開中の状態
false:折りたたまれている状態

aria-labelledby="IDREF"

[対象:svg要素]音声読み上げ等の支援技術を利用するユーザーがハンバーガーメニューの目的を理解することを手助けします。

メニューバー関連(永続的に見えるメニュー)

role="menubar"

[対象:ul要素]視覚的に永続的なメニューは、メニューバーとして定義します。

aria-label="string"

[対象:ul要素]音声読み上げ等の支援技術を利用するユーザーがメニューバーの目的を理解することを手助けします。

role="menuitem"

[対象:a要素]メニューバーの項目として定義します。

aria-current="page"

[対象:a要素]現在のページのURL同じURLのリンクを持つ要素に定義します。

aria-haspopup="true"

[対象:a要素]メニュー項目にサブメニューが含まれているかどうかを表します。
true:サブメニュー有り
false:サブメニュー無し

role="none"

[対象:li要素]要素の暗黙のロールを削除します。

隠されたメニュー

aria-hidden="true"

[対象:div要素]視覚的・聴覚的に隠された要素を表します。

role="menu"

[対象:ul要素]永続的ではないメニューは、メニューとして定義します。

role="menuitem"

[対象:a要素]メニューの項目として定義します。

WAI-ARIAの実装

では、早速先ほど説明したWAI-ARIAを用いて、ナビゲーションメニューにWAI-ARIAを取り入れてみましょう。

<nav id="global-menu" aria-label="グローバル">
    
    <!-- PC向けメニュー -->
    <ul role="menubar" class="desktop" id="desktop-menubar" aria-label="メインナビゲーション">
        <li role="none" class="logo"><a role="menuitem" href="#">タイトル</a></li>
        <li role="none" class="link"><a role="menuitem" href="#">PC MENU1</a></li>
        <li role="none" class="link"><a role="menuitem" href="#">PC MENU2</a></li>
        <li role="none" class="link"><a role="menuitem" href="#">PC MENU3</a></li>
    </ul>

    <!-- SP向けメニュー -->
    <ul role="menubar" class="mobile" id="mobile-menubar"  aria-label="メインナビゲーション">
        <li role="none">
            <button 
            id="global-menu-button"
            type="button"
            aria-expanded="false" 
            aria-controls="global-menu-overlay"
            data-close-label="メインメニューを閉じる"
            data-open-label="メインメニューを開く"
            aria-haspopup="true"
            >
            <svg 
            role="img"
            id="global-menu-img"
            aria-labelledby="global-menu-title"
            xmlns="http://www.w3.org/2000/svg" 
            viewBox="0 0 23 15">
            <title id="global-menu-title">メインメニューを開く</title>
            <g><line class="top" x1="0" y1="0.5" x2="23" y2="0.5"/>
            <line class="middle" x1="0" y1="7.5" x2="23" y2="7.5"/>
            <line class="bottom" x1="0" y1="14.5" x2="23" y2="14.5"/></g>
            </svg>
            </button>
        </li>
        <li role="none" class="logo" ><a role="menuitem" href="#">タイトル</a></li>
    </ul>

    <!-- ハンバーガーメニューをタップした場合に表示する内容 -->
    <div id="global-menu-overlay" aria-hidden="true" aria-labelledby="global-menu-title">
        <ul role="menu">
            <li role="none" class="link"><a role="menuitem" href="#">SP MENU1</a></li>
            <li role="none" class="link"><a role="menuitem" href="#">SP MENU2</a></li>
            <li role="none" class="link"><a role="menuitem" href="#">SP MENU3</a></li>
        </ul>
    </div>

</nav>

ハンバーガーメニューを制御しよう

ここまで出来れば後は簡単ですね。ハンバーガーボタンがタップされた場合に、aria-expandedやaria-hidden等の属性の値を変更するJavaScriptを追加します。 ボタンが押された場合に、svgのタイトルも"メインメニューを開く"から"メインメニューを閉じる"に変更するようにしています。 また、オーバーレイメニューが表示された場合、body要素にclipというclass名を追加しています。

{
    const globalMenuButton = document.querySelector('#global-menu-button');
    const globalMenuImg    = document.querySelector('#global-menu-img');
    const body             = document.querySelector('body');

    let openLabel   = globalMenuButton.dataset.openLabel;
    let closeLabel  = globalMenuButton.dataset.closeLabel;

    let overlayMenu = document.querySelector(`#${globalMenuButton.getAttribute('aria-controls')}`);
    let menuTitle   = document.querySelector(`#${globalMenuImg.getAttribute('aria-labelledby')}`);

    const toggleRegion = () => {
        if (globalMenuButton.getAttribute('aria-expanded') == 'false') {
            globalMenuButton.setAttribute('aria-expanded', 'true');
            overlayMenu.setAttribute('aria-hidden', 'false');
            menuTitle.innerHTML = closeLabel;
            globalMenuButton.focus();
            body.classList.add('clip');
        } else {
            globalMenuButton.setAttribute('aria-expanded', 'false');
            overlayMenu.setAttribute('aria-hidden', 'true');
            menuTitle.innerHTML = openLabel;
            body.classList.remove('clip');
        }
    };
    globalMenuButton.addEventListener('click', toggleRegion);
}


QUESTION


  • avatar
    画面上変化がないんだけど?

    どういうことだ?JavaScriptを実行しても、画面に変化が無いぞ?

  • 何言ってるの?

    WAI-ARIAは、見た目には影響がないわ。JavaScriptも属性を変更しているだけだもの。

    avatar
  • avatar
    表示の切り替えはどうやるんだい?

    ハンバーガーメニューの目的は、隠されたメニューの表示・非表示を切り替えることだと思ったんだが?どうやって見た目の切り替えをするんだ。

  • 当然CSSよ。

    見た目の変更はCSSで制御していくわ。そのためには、しっかりHTMLの構造を作ることが大切ね。見た目とらわれず頑張っていきましょう。

    avatar
  • avatar
    JavaScriptを無効にしている場合は?

    javaScriptでWAI-ARIAの切り替えを行っているけど、JavaScriptを無効にしている場合どうする?

  • よくある質問だわ。

    確かに、JavaScriptを無効にしている場合動かない。そのために、ハンバーガーメニューをJavaScriptを使わずに作ろうとする人も一定数いる。
    だけど、ハンディキャップのある方がサイトを使用する場合必ずWAI-ARIAは助けになる。もし、JavaScriptが無効な場合を気にするのであれば、noscript等で代替メニューを準備すべきね。
    たまに勘違いしている人がいるけど、noscriptタグを利用してJavaScriptをONにしてくれって案内、なんの冗談かしら?
    noscriptタグは、JavaScriptが無効な場合でもサイトが動くように配慮する機能を盛り込む場所だわ。

    avatar

CSSで表示を制御してみよう!

さて、長い長いHTMLとJavaScriptの作成が終わってようやく見た目を作るときがやってきました。ここでも少しずつCSSを追加しながら ハンバーガーメニューの装飾をしていきましょう。(とは言っても、このコンテンツでの目標はすでに達成しているので、最低限のレイアウトしかこの記事では行いません。)

画面サイズ合わせて表示切り替え

まず取り掛からなくてはならないことは、表示画面幅によって表示するメニューを切り替えることです。 モバイルファーストでレイアウトを行い、デスクトップ系レイアウトはブレイクポイント側で実施します。

    /* リセット系 ****************************************************************/
    nav#global-menu ul { margin:0; padding:0; list-style:none; }
    nav#global-menu #global-menu-button { margin:0; padding:0; border-radius:0; background:transparent; border:none;outline:none; appearance:none; }

    /* モバイルレイアウト系 ******************************************************/


    /* ハンバーガーメニュー系*****************************************************/
    nav#global-menu ul.mobile #global-menu-button { width:49px; height:49px; cursor:pointer; }
    nav#global-menu ul.mobile #global-menu-button svg { width:23px; height:15px; }
    nav#global-menu ul.mobile #global-menu-button svg line { stroke:#333; }
    nav#global-menu ul.mobile #global-menu-button { display:inline-flex; align-items:center; justify-content:center; }

    /* オーバーレイメニュー系 ****************************************************/
    nav#global-menu #global-menu-overlay { width:100%; height:100vh; visibility:hidden; overflow:hidden; }
    nav#global-menu #global-menu-overlay[aria-hidden="false"] { visibility:visible; overflow:auto;}


    /* PC環境の調整 *************************************************************/
    nav#global-menu ul.desktop { display:none; }                /* PCメニュー非表示 */
    
    /* レイヤー管理 *************************************************************/
    nav#global-menu #mobile-menubar      { position:relative; z-index:110; } /* 上 */
    nav#global-menu #global-menu-overlay { position:absolute; z-index:100; } /* 下 */

    /* 以下モニタサイズごとの調整 ************************************************/
    @media (min-width: 576px) {
    }
    @media (min-width: 768px) {
      nav#global-menu ul.mobile { display:none; }               /* SPメニュー非表示 */
      nav#global-menu #global-menu-overlay{ display:none; }     /* SPメニュー非表示 */
      nav#global-menu ul.desktop { display:block; }             /* PCメニュー表示   */
    }
    @media (min-width: 992px) {
    }
    @media (min-width: 1200px) {
    }

ハンバーガーメニューを上部に固定

ここから少しずつ装飾してみましょう。まずはハンバーガーメニューをページ上部に固定し、スクロールしても常に表示されるように変更します。 ページ内に固定するためには、position:fixedを利用することで簡単に実現できます。また、メニュー高をハンバーガーメニューサイズと同じ49pxで作成するため、bodyにメニュー分のマージンを追加していきます。

/* bodyにメニュー高さ分のマージンを追加 */
body{margin:0; margin-top:49px;} 

/* メニュー部に背景色を追加及びモバイルメニューをdisplay:flexを用いて横並びに変更 */
nav#global-menu ul.mobile { display:flex; width:100%; background:#E66EB2; }

/* ハンバーガーメニューを上部に固定 */
nav#global-menu { position:fixed; top:0; left:0; width:100%; }

/* PC版場合のグローバルメニューの上部固定を解除 */
@media (min-width: 768px) {
    nav#global-menu { position:static; width:auto; height:auto; background:transparent; }
    body{margin:0;}
}

ページ下部に固定する場合には、topをbottomに変更すれば可能です。

/* ハンバーガーメニューを上部に固定 */
nav#global-menu { position:fixed; bottom:0; left:0; width:100%; }

ハンバーガーメニューを×マークに変更

ハンバーガーメニューがタップされ、隠されたメニューが表示された際に、形を×マークに変更するようにしてみましょう。 やることは単純で、上下のバーを45度回転させ、中央のバーをopacity:0として非表示にさせています。

    /* 追加コード:svg アニメーション */
    nav#global-menu ul.mobile #global-menu-button svg line { transition: all 0.2s ease; }
    nav#global-menu #global-menu-button line.top    { transform-origin: 11.5px  0.5px; }
    nav#global-menu #global-menu-button line.bottom { transform-origin: 11.5px 14.5px; }
    nav#global-menu #global-menu-button[aria-expanded="true"] line.top    { transform:translate(0px, 7px) rotate(45deg); }
    nav#global-menu #global-menu-button[aria-expanded="true"] line.bottom { transform:translate(0px,-7px) rotate(-45deg); }
    nav#global-menu #global-menu-button[aria-expanded="true"] line.middle { transform:translate(-20px, 0px); opacity: 0; }

サブメニューをアニメーション

次に隠されたサブメニューの表示をアニメーションさせてみましょう。 translateXを変更すると様々な方向からサブメニューを出現させることができます。

    /* オーバーレイメニュー アニメーション系 左から右Ver */
    nav#global-menu #global-menu-overlay > ul { padding-top:40px; padding-left:20px; padding-right:20px; }
    nav#global-menu #global-menu-overlay { background:#eb8ec4 ; transform: translateX(-120vw); transition: all 0.2s ease-out; }
    nav#global-menu #global-menu-overlay[aria-hidden="false"] { transform: translateX(0); }

    /* オーバーレイメニュー アニメーション系 上から下Ver */
    nav#global-menu #global-menu-overlay > ul { padding-top:40px; padding-left:20px; padding-right:20px; }
    nav#global-menu #global-menu-overlay { background:#eb8ec4 ; transform: translateY(-120vh); transition: all 0.2s ease-out; }
    nav#global-menu #global-menu-overlay[aria-hidden="false"] { transform: translateY(0); }

bodyスクロールの抑制

個人的に気になるサブメニュー表示時にbodyまでスクロールが影響してしまうことを抑制しましょう。 これは、行っても行わなくてもどちらでも構いません。

    /* サブメニュー表示時のbodyスクロールの抑制 */
    body.clip { overflow:hidden; }

フォーカス時のハイライト表示

キーボードのタブキーなどでハンバーガーメニューを操作を行う場合、視覚的にわかりやすく表示してみましょう。

    /* フォーカス/ホバー時のハイライト表示 */
    nav#global-menu ul.mobile #global-menu-button:focus,
    nav#global-menu ul.mobile #global-menu-button:hover { outline:1px dotted #fff; background:rgba(0,0,0,.1); }

キーボード操作の優先順位の変更

タブキーを操作した場合、ハンバーガーメニュー→サイトタイトルと移動してしまいます。これをサイトタイトル→ハンバーガーに移動するように移動順序を変更しましょう。 また、この変更により、ハンバーガーメニューを開いた場合に、すぐに隠されたメニューにタブで移動することができます。 これには、htmlのtabindexを用いて順序を変更します。

        <!-- SP向けメニュー -->
        <ul role="menubar" class="mobile" id="mobile-menubar" aria-label="メインナビゲーション">
            <li role="none">
                <button tabindex="2" type="button" id="global-menu-button" aria-expanded="false" aria-controls="global-menu-overlay"
                    data-close-label="メインメニューを閉じる" data-open-label="メインメニューを開く" aria-haspopup="true">
                    <svg role="img" id="global-menu-img" aria-labelledby="global-menu-title"
                        xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 23 15">
                        <title id="global-menu-title">メインメニューを開く</title>
                        <g><line class="top" x1="0" y1="0.5" x2="23" y2="0.5" />
                        <line class="middle" x1="0" y1="7.5" x2="23" y2="7.5" />
                        <line class="bottom" x1="0" y1="14.5" x2="23" y2="14.5" /></g>
                    </svg>
                </button>
            </li>
            <li role="none" class="logo"><a tabindex="1" role="menuitem" href="">タイトル</a></li>
        </ul>


完成形

では、最終的に完成したコードを見てみましょう。結構スッキリまとまった感じですね。 もちろん、最低限のハンバーガーメニューの機能を盛り込んだ構成になっていますので、カスタマイズは必ず必要です。

HTML

<nav id="global-menu" aria-label="グローバル">

    <!-- PC向けメニュー -->
    <ul role="menubar" class="desktop" id="desktop-menubar" aria-label="メインナビゲーション">
        <li role="none" class="logo"><a role="menuitem" href="#">タイトル</a></li>
        <li role="none" class="link"><a role="menuitem" href="#">PC MENU1</a></li>
        <li role="none" class="link"><a role="menuitem" href="#">PC MENU2</a></li>
        <li role="none" class="link"><a role="menuitem" href="#">PC MENU3</a></li>
    </ul>

    <!-- SP向けメニュー -->
    <ul role="menubar" class="mobile" id="mobile-menubar"  aria-label="メインナビゲーション">
        <li role="none">
            <button 
            tabindex="2"
            type="button"
            id="global-menu-button"
            aria-expanded="false"
            aria-controls="global-menu-overlay"
            data-close-label="メインメニューを閉じる"
            data-open-label="メインメニューを開く"
            aria-haspopup="true"
            >
            <svg 
            role="img"
            id="global-menu-img"
            aria-labelledby="global-menu-title"
            xmlns="http://www.w3.org/2000/svg" 
            viewBox="0 0 23 15">
            <title id="global-menu-title">メインメニューを開く</title>
            <g><line class="top" x1="0" y1="0.5" x2="23" y2="0.5"/>
            <line class="middle" x1="0" y1="7.5" x2="23" y2="7.5"/>
            <line class="bottom" x1="0" y1="14.5" x2="23" y2="14.5"/></g>
            </svg>
            </button>
        </li>
        <li role="none" class="logo" ><a tabindex="1" role="menuitem" href="#">タイトル</a></li>
    </ul>

    <!-- ハンバーガーメニューをタップした場合に表示する内容 -->
    <div id="global-menu-overlay" aria-hidden="true" aria-labelledby="global-menu-title">
        <ul role="menu">
            <li role="none" class="link"><a role="menuitem" href="#">SP MENU1</a></li>
            <li role="none" class="link"><a role="menuitem" href="#">SP MENU2</a></li>
            <li role="none" class="link"><a role="menuitem" href="#">SP MENU3</a></li>
        </ul>
    </div>

</nav>

JavaScript

{
    const globalMenuButton = document.querySelector('#global-menu-button');
    const globalMenuImg    = document.querySelector('#global-menu-img');
    const body             = document.querySelector('body');

    let openLabel   = globalMenuButton.dataset.openLabel;
    let closeLabel  = globalMenuButton.dataset.closeLabel;

    let overlayMenu = document.querySelector(`#${globalMenuButton.getAttribute('aria-controls')}`);
    let menuTitle   = document.querySelector(`#${globalMenuImg.getAttribute('aria-labelledby')}`);

    const toggleRegion = () => {
        if (globalMenuButton.getAttribute('aria-expanded') == 'false') {
            globalMenuButton.setAttribute('aria-expanded', 'true');
            overlayMenu.setAttribute('aria-hidden', 'false');
            menuTitle.innerHTML = closeLabel;
            globalMenuButton.focus();
            body.classList.add('clip');
        } else {
            globalMenuButton.setAttribute('aria-expanded', 'false');
            overlayMenu.setAttribute('aria-hidden', 'true');
            menuTitle.innerHTML = openLabel;
            body.classList.remove('clip');
        }
    };
    globalMenuButton.addEventListener('click', toggleRegion);
}

CSS


    /* リセット系 ***************************************************************/
    nav#global-menu ul { margin:0; padding:0; list-style:none; }
    nav#global-menu #global-menu-button {margin:0; padding:0; border-radius:0; background:transparent; border:none; outline:none; appearance:none; }

    /* モバイルレイアウト系 ******************************************************/
    nav#global-menu ul.mobile { display:flex; width:100%; background:#E66EB2; }
    body { margin:0; margin-top:49px; }

    /* ハンバーガーメニュー系 ****************************************************/
    nav#global-menu ul.mobile #global-menu-button { width:49px; height:49px; cursor:pointer; }
    nav#global-menu ul.mobile #global-menu-button svg { width:23px; height:15px; }
    nav#global-menu ul.mobile #global-menu-button svg line { stroke:#fff; }
    nav#global-menu ul.mobile #global-menu-button { display:inline-flex;align-items:center; justify-content: center;}

    /* オーバーレイメニュー系 ****************************************************/
    nav#global-menu #global-menu-overlay { width:100%; height:100vh; visibility: hidden; overflow:hidden; }
    nav#global-menu #global-menu-overlay[aria-hidden="false"] { visibility:visible; overflow:auto; }
    nav#global-menu #global-menu-overlay > ul { padding-top:40px; padding-left:20px; padding-right:20px; }

    /* オーバーレイメニュー アニメーション系 **************************************/
    nav#global-menu #global-menu-overlay { background:#eb8ec4; transform:translateX(-120vw); transition:all .2s ease-out; }
    nav#global-menu #global-menu-overlay[aria-hidden="false"] { transform:translateX(0); }

    /* svg アニメーション系 *****************************************************/
    nav#global-menu ul.mobile #global-menu-button svg line { transition:all 0.2s ease; }
    nav#global-menu #global-menu-button line.top    { transform-origin:11.5px  0.5px; }
    nav#global-menu #global-menu-button line.bottom { transform-origin:11.5px 14.5px; }
    nav#global-menu #global-menu-button[aria-expanded="true"] line.top    { transform:translate(0px, 7px) rotate(45deg); }
    nav#global-menu #global-menu-button[aria-expanded="true"] line.bottom { transform:translate(0px,-7px) rotate(-45deg); }
    nav#global-menu #global-menu-button[aria-expanded="true"] line.middle { transform:translate(-20px, 0px); opacity:0; }

    /* ハンバーガーメニューを上部に固定 ********************************************/
    nav#global-menu { position:fixed; top:0; left:0; width:100%; }

    /* サブメニュー表示時のbodyスクロールの抑制 ************************************/
    body.clip { overflow:hidden; }

    /* フォーカスホバー時のハイライト表示 ******************************************/
    nav#global-menu ul.mobile #global-menu-button:focus,
    nav#global-menu ul.mobile #global-menu-button:hover { outline:1px dotted #fff; background:rgba(0,0,0,.1); }

    /* PC環境の調整 *************************************************************/
    nav#global-menu ul.desktop { display:none; }                /* PCメニュー非表示 */
    
    /* レイヤー管理 *************************************************************/
    nav#global-menu #mobile-menubar      { position:relative; z-index:110; } /* 上 */
    nav#global-menu #global-menu-overlay { position:absolute; z-index:100; } /* 下 */

    /* 以下モニタサイズごとの調整 ************************************************/
    @media (min-width: 576px) {
    }
    @media (min-width: 768px) {
      nav#global-menu ul.mobile { display:none; }               /* SPメニュー非表示 */
      nav#global-menu #global-menu-overlay{ display:none; }     /* SPメニュー非表示 */
      nav#global-menu ul.desktop { display:block; }             /* PCメニュー表示   */
      
      /* ハンバーガーメニューを上部に固定解除 */
      nav#global-menu { position:static; height:auto; width:auto; background:transparent; }
      body { margin:0; }
    }
    @media (min-width: 992px) {
    }
    @media (min-width: 1200px) {
    }


QUESTION


  • avatar
    メニューの位置を変えたいんだけど。

    今左にメニューがあるけど、これを右にできないかい?

  • 簡単よ。

    nav#global-menu ul.mobileに対して、flex-direction: row-reverse;を追加すれば右に移動できるわ。

    右Ver

    avatar
  • avatar
    サイズの変更がめんどくさいなー

    サイズや色を変える時に、修正漏れが心配なんだが・・・。もう少し値を集約できないか?

  • CSS カスタムプロパティの出番ね。

    CSSのカスタムプロパティを使えば、何度も登場するCSSの設定値や色等をまとめてに保存しておくことで、CSSの様々な場所で参照することができるわ。
    IE11は非対応だから、ここでは紹介だけにとどめておくわね。

    カスタムプロパティ

    avatar
  • avatar
    見た目が中途半端じゃないか?

    もう少しどうにかならないのか?

  • できるけど・・・・

    だけど、見た目を細かく調整していたら、それだけで膨大なCSSになってしまう。後は自分の力でやりなさい。

    avatar

おまけ、CSSだけで動くように

念の為、JSがOFFの場合にCSSだけで動くように変更してみましょう。 CSSでハンバーガーメニューを動かす手段としては、チェックボックスを利用した表示のON・OFFが挙げられます。 代表的にな実装例としては、Appleのサイトで採用されています。

HTMLにinput要素を追加

global-menuの直下にinput要素を追加し、JavaScriptが有効な場合には非表示に設定します。

        <!-- JavaScriptが無効な場合にも、CSSだけで動くように -->
        <input class="mobile" id="mobile-menu-state" type="checkbox" name="checkbox-mobile-menu-state">
        <script>
        { document.querySelector('#mobile-menu-state').style.display ='none'; }</script>

CSSにチェックボックスがONの場合の動作追加

チェックボックスが有効な場合に、svgとオーバーレイメニューが動作するように調整をします。


    /* CSSだけで動くように調整 **************************************************/
    nav#global-menu input#mobile-menu-state { position:absolute; left:0; top:0; width:49px; height:49px; }
    nav#global-menu input#mobile-menu-state { z-index: 120; appearance:none; margin:0; padding:0; border:none; background:none; }
    nav#global-menu input#mobile-menu-state:hover,
    nav#global-menu input#mobile-menu-state:focus{ border:1px dotted #fff;	background:rgba(0,0,0,.1);}
    nav#global-menu input#mobile-menu-state:checked ~ ul#mobile-menubar line.top    { transform:translate(0px, 7px) rotate(45deg); }
    nav#global-menu input#mobile-menu-state:checked ~ ul#mobile-menubar line.bottom { transform:translate(0px,-7px) rotate(-45deg); }
    nav#global-menu input#mobile-menu-state:checked ~ ul#mobile-menubar line.middle  { transform:translate(-20px, 0px); opacity:0; }
    nav#global-menu input#mobile-menu-state:checked ~ div#global-menu-overlay { transform: translateX(0);  visibility:visible; overflow:auto;}
    @media (min-width: 768px) {
      nav#global-menu input#mobile-menu-state { display:none; } /* SPメニュー非表示 */
    }


COMPLETE


  • avatar
    お疲れ様でした!

    今回は基本機能と基本的なレイアウトしか実装していませんが、皆さんの力で素敵な見た目を追加してみてください。 まずは骨格たるHTMLを。次に見た目を。優先順位は見た目ではないかな? メニュー部分にh1を使う場合は十分注意してね。詳しくは、HTML入門6を御覧ください。 さっきは自分でやれって言ったけど、デザイン編を執筆します。


SKILL


習得スキルボード

  • ハンバーガーメニューの作り方
  • メディアクエリーの初歩
  • WAI-ARIA基礎

コメント