17
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

LIFULLAdvent Calendar 2022

Day 20

Stimulusでa11yを考慮したハンバーガーボタンを実装する

Last updated at Posted at 2022-12-20

Stimulusとは

Stimulusとは、JavaScriptで書かれたクライアントサイドのライブラリです。
有名どころだとStack Overflowが採用していたりします。

最近巷で話題のStimulusですが、バージョン3.2が11月29日にリリースされました。

私は中でもキーボードイベントのフィルタリング構文がとても使いやすいと思っていて、StimulusはHTMLを見ただけで大体の振る舞いが分かるよう実装されるので、アクセシビリティを考慮した実装がさらにやりやすくなったと感じています。

ハンバーガーボタンとは

よく右上とか左上とかにある三本線のアレです。
ハンバーガーボタン、みんな大好きですよね。

でも実装方法は人によって様々あるように感じています。
今回はこのハンバーガーボタン(とハンバーガーメニュー)を、アクセシビリティを考慮しつつ、Stimulusで実装してみたいと思います。

アクセシビリティを考慮したハンバーガーボタン

ハンバーガーボタンとは言っても、要はボタンとメニューの表示方法が少し特徴的なモーダルダイアログだと言えます。
つまりARIA Authoring Practices GuideのDialog (Modal)に従って実装すればOKです。

HTMLを書いてみる

<div>
  <div class="overlay"></div>
  <button type="button"
          aria-label="メニューを開く"
          aria-haspopup="dialog"
          aria-controls="hamburgerMenu"
  ><img src="https://example.com/bar.png" alt=""></button>
  <div role="dialog"
       aria-labelledby="menuTitle"
       aria-modal="true"
       aria-hidden="true"
       id="hamburgerMenu"
  >
    <h2 id="menuTitle">EXAMPLE MENU</h2>
    <ul>
      <li><a href="https://example.com/" tabindex="-1">MENU 1</a></li>
      <li><a href="https://example.com/" tabindex="-1">MENU 2</a></li>
      <li><a href="https://example.com/" tabindex="-1">MENU 3</a></li>
      <li><a href="https://example.com/" tabindex="-1">MENU 4</a></li>
      <li><a href="https://example.com/" tabindex="-1">MENU 5</a></li>
    </ul>
    <button type="button"
            aria-label="メニューを閉じる"
            tabindex="-1"
    ><img src="https://example.com/cross.png" alt=""></button>
  </div>
</div>

要素の説明

要素 属性 説明
<div> 全体のラッパー。
<div> class="overlay" メニュー表示時のレイヤー。
<button> type="button" ボタン。
aria-label="メニューを開く" ボタンの説明。
aria-haspopup="dialog" このボタンをクリックすると何が起こるのか。
今回はダイアログが開くことを示している。
aria-controls="hamburgerMenu" どのモーダルダイアログを制御しているか。
その要素のidに紐づく。
<img> alt="" ハンバーガーアイコン。
親の<button>要素のaria-labelで説明されているので、altは空でOK。
<div> role="dialog" dialogロール
いわゆるハンバーガーメニュー。
aria-labelledby="menuTitle" どの要素にラベリングされているか。
後述の<h2 id="menuTitle">に紐づく。
つまり、このメニューのラベルは「EXAMPLE MENU」となる。
aria-modal="true|false" その要素がモーダルダイアログ(true)かそうじゃない(false)か。
モーダルウィンドウのコンテナ要素に aria-modal 属性を適用するが分かりやすい。
今回この要素はモーダルなのでtrueをセット。
aria-hidden="true|false" アクセシビリティツリーから消すか消さないか。
消すならtrue、消さないならfalse
最初は閉じているので、音声読み上げもされないように初期値はtrueをセット。
id="hamburgerMenu" どの<button>要素から制御されているかを示すためのID。
<h2> id="menuTitle" どの要素から参照されているか(今回の場合はaria-labelledbyを示すためのID。
<ul> 順序なしリスト。
<li> リストアイテム。
<a> href="https://example.com/" リンク。リンク先は例。
tabindex="0|-1" フォーカス可能(0)か不可(-1)か。
メニューが閉じているときはフォーカスされたくないので、初期値は-1をセット。
<button> type="button" ボタン。
aria-label="メニューを閉じる" ボタンの説明。
tabindex="0|-1" フォーカス可能(0)か不可(-1)か。
メニューが閉じているときはフォーカスされたくないので、初期値は-1をセット。

以上で意味的にはアクセシブルなモーダルダイアログの完成です。
次にStimulusで振る舞いをアタッチしていきます。
この振る舞い(動き)によって、このモーダルダイアログがいわゆるハンバーガーボタンになっていくのです。

Hamburger Controller

与えたい振る舞いとしてはボタンのクリックやキーボード操作による挙動、範囲外クリックによるモーダルの表示非表示です。
全体のラッパーにdata-controller="hamburger"を付与して、Hamburger Controllerを作ります。

「ハンバーガーコントローラー」ってなんだよって思いますよね。私もそう思います。

<div data-controller="hamburger">
  <div class="overlay"></div>
  .
  .
  .
</div>

次に取得したい要素にdata-hamburger-target属性を付与します。
今回の対象要素はオーバーレイ、ボタン、メニュー、メニュー内のフォーカス可能な要素(リンクとボタン)です。

オーバーレイとボタン
<div class="overlay"
     data-hamburger-target="overlay"
></div>
<button type="button"
        aria-label="メニューを開く"
        aria-haspopup="dialog"
        aria-controls="hamburgerMenu"
        data-hamburger-target="openBtn"
>
メニュー
<div role="dialog"
     aria-labelledby="menuTitle"
     aria-modal="true"
     aria-hidden="true"
     id="hamburgerMenu"
     data-hamburger-target="menu"
>
  <h2 id="menuTitle">EXAMPLE MENU</h2>
  <ul>
    <li>
      <a href="https://example.com/"
         tabindex="-1"
         data-hamburger-target="tabbableElm"
      >MENU 1</a>
    </li>
    .
    .
    .
  </ul>
  <button type="button"
          aria-label="メニューを閉じる"
          tabindex="-1"
          data-hamburger-target="tabbableElm closeBtn"
  ><img src="https://example.com/cross.png" alt=""></button>
</div>

モーダルダイアログ(ハンバーガーボタン)に求められる要件

次にdata-action属性を付与して要件を満たしていきます。
基本的には「開くボタン」をクリックしたらメニューが開く、「閉じるボタン」をクリックしたらメニューが閉じる、メニュー外をクリックしたらメニューが閉じる、という振る舞いでOKです。

次にキーボード操作による振る舞いですが、以下でモーダルダイアログのキーボードインタラクションについて見ていきます。

キーボード操作 説明
Tab メニュー内の次のフォーカス可能な要素にフォーカスを移動する。
メニュー内の最後のフォーカス可能な要素にフォーカスがある場合、最初のフォーカス可能な要素にフォーカスを移動する。
Shift + Tab メニュー内の前のフォーカス可能な要素にフォーカスを移動する。
メニュー内の最初のフォーカス可能な要素にフォーカスがある場合、最後のフォーカス可能な要素にフォーカスを移動する。
Escape メニューを閉じる。

data-action属性の値は{event}->{controller}#{method_name}のフォーマットで記述します。
今回使うイベントはclickkeydownの2つです。
冒頭でも述べましたが、バージョン3.2になったことでキーボードイベントのフィルタ構文が使えるようになりました。
つまり、keydownイベントを書いてJS内でキーごとに条件分岐せずとも、今回の場合だとkeydown.tabkeydown.shift+tabkeydown.escとそれぞれ書くことができるのです。便利!

Stimulusでの実装例

基本的にやることは、{name}Target or {name}Targetsで対象要素を取得し、アクティブ、非アクティブな要素に必要な属性値を付け替えることです。

あとはDialog (Modal)のNOTEに従って振る舞いをアタッチしていきます。
具体的には以下の2項目です。

  1. メニューを開いたときにメニュー内の最初のフォーカス可能な要素にフォーカスを移動する
  2. メニューを閉じたときに「開くボタン」にフォーカスを移動する

1.の理由は、メニューが開いたときにフォーカスを移動しないと、支援技術を利用しているユーザーにはメニューが開いたのかどうかが分からない、もしくはメニューがどこにあるのかが分からないからです。

2.の理由は、メニューを閉じたときにフォーカスを移動しないと、支援技術を利用しているユーザーには一瞬どこにいるのか分からなくなるからです。
このとき、「開くボタン」にフォーカスが移動すれば、メニューを開く直前の場所に戻ってきたことになるので、現在位置を把握しやすくなります。

また、独自に以下の2項目の振る舞いも追加します。

  • メニュー内でのフォーカスのループ
  • メニュー内をクリックしたか、メニュー外をクリックしたかの判定

See the Pen QIITA_STIMULUS_ACCESIBLE_HAMBURGER_BUTTON by yoruaki (@yoruaki) on CodePen.

状態を管理するValues APIを一部利用しているのですが、もう少しスマートに実装できたかもしれません。日々勉強ですね。

あとはCSSの話になりますが、ハンバーガーボタンをクリックすると、メニューがニョキッと出現するようにしています。
また、メニュー内の「閉じるボタン」以外に、メニュー外をクリックした際にもメニューが閉じるように(引っ込むように)しています。

これだけで一気にハンバーガーボタンっぽくなりますね。

今回追加したdata-action属性

実装内容の詳細については説明しませんが、今回追加したdata-action属性の値について列挙してみます。
これらの値をHTML上から見てみて、どんな振る舞いを持っているかがおおよそ判断できるなら、Stimulusとしての目的は果たせているのではないでしょうか。

data-action属性の値 説明
click->hamburger#detectClickArea クリックされたら、クリックエリアを判別する。
click->hamburger#openMenu クリックされたら、メニューを開く。
keydown.esc->hamburger#closeMenu Escapeキーが押されたら、メニューを閉じる。
keydown.tab->hamburger#nextMoveFocus Tabキーが押されたら、次の要素にフォーカスを移動する。
keydown.shift+tab->hamburger#prevMoveFocus Shift + Tabキーが押されたら、前の要素にフォーカスを移動する。
click->hamburger#closeMenu クリックされたら、メニューを閉じる。

最後に

Stimulusでアクセシビリティを考慮したハンバーガーボタンを実装してみました。

最近スマホサイトなどでよく見かけるようになりましたが、こういうインタラクションなUIの裏側で、きちんとアクセシビリティも考慮し、なるべく多くの人に情報を伝えていきたいですね。

17
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?