アクセシビリティを考慮したハンバーガーメニューのモダンな作り方

アクセシビリティを考慮したハンバーガーメニューのモダンな作り方

単純に見た目だけでハンバーガーメニューを作ることは簡単ですが、実際にコンピューターや視覚障害者に開閉状態を伝えるためにはアクセシビリティを考慮したコーディングが必要になります。

今回は以下の箇所に重点を置き、出来るだけシンプルな構造でハンバーガーメニューを作成しました。

  • メニューボタンはbutton要素で作成
  • 必要に応じてWAI-ARIA属性を付与
  • カスタマイズしやすい柔軟性がある設計
目次

実装での前提条件

  • アイコンをアニメーションさせる
  • キーボードで操作可能にする
  • 閉じたメニュー項目にフォーカスが当たらないようにする
  • 開閉状態をスクリーンリーダーで読み上げられるようにする
  • ボタンラベルやオーバーレイは簡単に表示・非表示にできる
  • 背景がスクロールしないようする
  • 背景をクリックしたときはメニューを閉じる
  • 開閉時にボタンラベルを切り替えられる

メニューボタンはbutton要素で作る

今回、メニューボタンにbutton要素を採用した理由は、アクセシビリティとセマンティクスの観点からです。

キーボード操作

button要素はデフォルトでキーボード操作が有効で、ユーザがフォーカスを当ててEnterキーを押すことで簡単に操作できます。

セマンティクス

ウェブブラウザや検索エンジン、スクリーンリーダーなどに要素が「ボタン」であることを明示的に示すことができます。

デザインの柔軟性

button要素は、子要素にアイコンやテキストを配置できるため、デザインの柔軟性を持たせることができます。

ハンバーガーメニューボタン

See the Pen humberger by hisa (@hisaaashi) on CodePen.

コーディングのポイントを絞って解説していきます。

ボタンの構造

<button type="button" id="menu-button" aria-expanded="false" aria-controls="menu" aria-label="メニュー">
  <span class="bar"></span>
  <span class="menu-label" id="menu-label" aria-hidden="true">MENU</span>
</button>

ボタンの構造は非常に単純ですね。

3本のバーアイコンはspan要素と擬似要素の::before::afterで作成しました。

アイコン下のMENUラベルが不要であれば、HTMLをコメントアウトするかCSSで非表示にするだけでOKです。

type属性について

type属性は、フォーム外では無効になるため必須ではありません。しかし、buttonを明示的に指定することで、意図が明確になり、予期せぬ動作を防ぐことができます。デフォルトではsubmitが設定されています。

ボタンにWAI-ARIAの付与

button要素にはアクセシビリティを高めるため、WAI-ARIAの属性を付与しています。

aria-expanded

要素が展開されているかどうかを示す属性。展開していればtrue、展開していない場合はfalseを指定。

aria-controls

制御対象となる要素を指定する属性。対象の要素のIDを指定して関係性を紐付けます。

aria-label

要素にラベル付けする属性。ラベル内のテキストが音声ソフトなどのスクリーンリーダーで読み上げられます。

aria-labelの補足

aria-label 属性が指定されている場合は、button要素内のテキストは無視され、代わりに優先的に読み上げられます。ただし、一部のアシスト技術は両方を読み上げる可能性があるため、テキスト要素にaria-hidden="true" 属性を追加して、テキストが重複して読み上げられるのを回避しています。

<!-- 良くない例:読まれない -->
<button><span class="bar"></span></button>

<!-- あまり良くない例:「MENU」も「メニューを開く」も読み上げる可能性がある -->
<button aria-label="メニューを開く">MENU</button>

<!-- 良い例:「メニューを開く」が読み上げられる -->
<button aria-label="メニューを開く"><span aria-hidden="true">MENU</span></button>
<button aria-label="メニューを開く"><span class="bar"></span></button>

WAI-ARIAは、本来は不足している情報を補完するために使用されるべきものであり、最低限の使用が推奨されています。

無闇に属性を追加してしまうと、予期しない構造になり、悪影響を及ぼす可能性があるため、セマンティックなHTML要素を利用することが最善です。

よって今回は、必要最低限の属性のみ付与しました。

ボタンのCSSのポイント

/* ハンバーガーメニュー */
#menu-button {
  position: fixed;
  right: 8px;
  display: grid;
  place-items: center;
  place-content: center;
  width: 60px;
  height: 60px;
  background: #ddd;
  border: none;
  cursor: pointer;
  z-index: 999;
}

ボタン要素にはflexよりも配置の自由度が高く、記述の短いgridを採用。

ラベルの有無を考慮し、子要素が複数の場合でもボタン中央に配置されるようplace-itemsplace-contentを併用しています。

この3行だけで全ての要素が中央寄せできるのは優秀ですね。

/* バー */
.bar,
.bar::before,
.bar::after {
  width: 25px;
  height: 3px;
  background-color: #333;
  transition: transform 0.3s;
}

.bar {
  display: grid;

  &::before,
  &::after {
    content: "";
    grid-area: 1 / 1;
  }

  &::before {
    transform: translateY(-8px);
  }

  &::after {
    transform: translateY(8px);
  }
}

/* オープン時のバー */
.menu-open {

  .bar {
    background-color: transparent;

    &::before {
      transform: rotate(45deg);
    }

    &::after {
      transform: rotate(-45deg);
    }
  }
}

アイコンのバーにもgridを採用し、擬似要素にはgrid-area:1/1で全ての要素が中心に重なるように配置しています。(3本が1本の線に見える状態)

これにより、アニメーションで斜めにした際にも上下の位置調節が不要になり、しっかりと中心で綺麗なXアイコンができるようになります。

メニューの表示

<nav id="menu" inert>
  <ul class="test">
    <!-- 略 -->
  </ul>
</nav>

メニューで重要な点は、非表示の時にメニュー項目にフォーカスが当たらないことです。

通常、この機能を実現するためにはCSSでpointer-events: nonetabindex="-1"などを指定する必要がありますが、コンテンツ全体を一括で非活性にするinert属性を採用しました。

inert属性は、子孫要素を含むコンテンツ全体を操作不能にすることができ、コンテンツを一時的に無効にするために非常に便利な属性です。

アクティブな時にはJavaScriptで取り除きます。

CSSのポイント

/* メニュー */
#menu {
  position: fixed;
  height: 100%;
  width: 300px;
  background-color: #fff;
  inset: 0;
  z-index: 998;
  overflow-y: auto;
  transform: translateX(100%);
  transition: transform 0.3s ease-out;
}

/* オープン時のメニュー */
.menu-open #menu {
  transform: translateX(0);
}

CSSではtransformで100%画面外に隠し、オープン時に0に戻すことでスライドアニメーションさせています。

重要なポイントとしては、メニュー項目が多くてもメニュー内をスクロールできるようoverflow-y:autoを指定することです。

背景のオーバーレイ

<div id="overlay"></div>
/* オーバーレイ */
#overlay {
  visibility: hidden;
  opacity: 0;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.5);
  z-index: 997;
  transition: opacity 0.3s;

  /* オープン時のオーバーレイ */
  .menu-open & {
    visibility: visible;
    opacity: 1;
  }
}

画面全体を覆うように、幅と高さを100%にしposition:fixedで固定しています。

display:noneで制御すると、ふわっと出現するアニメーションは出来なくなるので注意。

メニュー外の背景をクリックしたときにも閉じる動作は、次のJavaScriptで解説しています。

JavaScriptでの開閉の仕組み

const menuButton = document.getElementById("menu-button");
const menuLabel = document.getElementById("menu-label");
const menu = document.getElementById("menu");
const body = document.body;
const overlay = document.getElementById("overlay");

function toggleMenu() {
  if (!body.classList.contains("menu-open")) {
    body.classList.add("menu-open");
    menuButton.setAttribute("aria-expanded", "true");
    menuButton.setAttribute("aria-label", "閉じる");
    menu.inert = false;
    if (menuLabel) {
      menuLabel.textContent = "CLOSE";
    }
  } else {
    body.classList.remove("menu-open");
    menuButton.setAttribute("aria-expanded", "false");
    menuButton.setAttribute("aria-label", "メニュー");
    menu.inert = true;
    if (menuLabel) {
      menuLabel.textContent = "MENU";
    }
  }
}

if (menuButton) {
  menuButton.addEventListener("click", toggleMenu);
}

if (overlay) {
  overlay.addEventListener("click", toggleMenu);
}

各要素を取得し、body要素にmenu-openクラスがあるか無いかで開閉状態を制御しています。

  • menu-openがない:メニューが閉じた状態(通常時)
  • menu-openがある:メニューが開いた状態

それぞれの開閉状態に合わせて、クラスの追加や削除、属性値やテキストの書き換えをまとめて行っています。

属性値を切り替えないと、開いているのに閉じた状態と誤解される可能性があるため、忘れずに書き換えを行う必要があります。

メニュー外の背景をクリックで閉じる

メニュー外の背景(つまりオーバーレイ)をクリックした際にも閉じる挙動にするため、一連の動作(属性値などの切り替え)をtoggleMenu()という名前で関数化し、クリックイベントで処理されるようにしました。

要素がない場合のエラー対策

各要素がない場合のエラーを回避するため、if文を使用し要素が存在する場合にだけ処理するようにしています。

これによりボタンラベルやオーバーレイを非表示にした場合や、PCなどでハンバーガーメニューがない場合でも問題なく動作させることができます。

body要素で状態を管理する利点

body要素を使用して開閉状態を管理する利点は、CSSの先頭に同じ”オープン”クラスを追加することで状態を表現できることと、JavaScriptの記述も少なくて済むという点です。

昔は、それぞれの要素に個別にクラスを与えていたのですが、今考えると冗長でしかないですね…。

// ドロワーメニュー(良いとは言えない記述例)
jQuery('.drawer-icon').on('click',function(e) {
	e.preventDefault();

	jQuery('.drawer-icon').toggleClass('is-active');
	jQuery('.drawer-content').toggleClass('is-active');
	jQuery('.drawer-background').toggleClass('is-active');

	return false;
});

ちなみに参考にした記事はこちらです

メニュー展開時に背景をスクロールさせない

/* body */
body {
  overflow-x: clip;

  /* オープン時のbody   */
  .menu-open& {
    overflow: clip;
  }
}

body要素に対するoverflow:hiddenは、position:stickyが使えなかったり、ブラウザやiPhoneなどの端末によってはhtmlにつけないといけなかったり色々問題があるので、overflow:clipを採用することにしました。

iPhoneやMacで確認したところ、Safari、Chrome共に問題なくスクロール制御できました。

課題点:PCでメニューを開くと背景がガタつく

PCでメニューボタンを開いたとき、背景が固定されてスクロールバーが急に無くなってしまうので、スクロールバーの幅だけガタつきます。

対策としては、

  • PCでは背景をスクロールさせる
  • PCではハンバーガーメニューを採用しない
  • 常にスクロールバーをつける

などですかね。

様々なサイトをのぞいてみましたが、背景をスクロールさせている例が多かったです。

確かにPCだと画面が広いし、背景をスクロールさせても特に問題ない気もしますね。

まとめ:ハンバーガーメニューはあまり好きでなはい

元も子もないようなことを言いますが、ハンバーガーメニューって分かりにくいし、なかなか押されないし、めちゃくちゃこだわる必要ってあるのかなって思います。笑

ただ、Web制作している身としては採用率高いし、実装するからには良いものを作成したい!という思いがあります。

今回のハンバーガーメニューはベースとしても使いやすいので、ぜひ参考にしてみてください。

おすすめWEBスクール

WEB制作やWEBデザインを学びたいなら、SNSでも話題の「デイトラ」がおすすめ!
どのコースも10万円前後と業界最安値で、副業や転職に向けて十分なスキルを身につけることができます。

\ ここから飛べます! /

役に立ったら他の方にシェア

お気軽にコメントどうぞ

コメントする

目次