【JavaScript】タブ切り替えメニュー:ページ内に複数設置可能

だいぶ久しぶりの更新となってしまいましたが、
今回はJavaScriptでTabメニューの実装をしていきます。

仕様

  • タブは3つ以上ある
  • クリックすると切り替わる
  • 表示されているタブは色が付く
  • 同じタブモジュールをページ内に複数設置することが可能

ポイントとしては、「同じタブモジュールをページ内に複数設置することが可能」という部分ですね。
実装にあたりいろいろググったのですが、JavaScriptで複数設置を想定した記事が少なく、
その条件をクリアするのが難しかったです。

また、tabと本文についてはJavaScriptのカスタムデータ属性を使い紐付けています。

実際に書いたコード


See the Pen
【JavaScript】タブメニュー
by naomi homma (@naomi-homma)
on CodePen.

コードの解説

JavaScriptのポイント

まずは、全てのtabにイベントリスナーを設定します。
この時配列用メソッドが使えるように「document.querySelectorAll」で要素の取得を行います。
「querySelectorAll」は「NodeList」を返しforEachメソッドが使えます。

developer.mozilla.org

要素の取得方法についてはこちらを参考にさせて頂きました。
qiita.com

//全てのtabにイベントリスナー設定
  const tabs = document.querySelectorAll('.tab__item')
  tabs.forEach((tab) => {
    tab.addEventListener('click', tabClick)
  })

次にtabと本文の表示切り替えを行うtabClick関数を書いていきます。

function tabClick(e) {
    //クリックされたtabのカスタムデータ属性の値を取得
    const tabTargetData = e.currentTarget.dataset.tab
    //クリックされたtabの親要素及びその子要素を取得
    const tabList = e.currentTarget.closest('.tab__list')
    const tabItems = tabList.querySelectorAll('.tab__item')
    //クリックされたtabの親要素と同じ親要素を持つcontentを取得(兄弟要素)
    const tabContentItems = tabList.nextElementSibling.querySelectorAll(
      '.tab__content-item'
    )
 ...途中略
  }

ここでポイントとなるのが、イベントが発生したtabの親要素を起点に
子要素・兄弟要素を取得し、それらに対してactiveクラスの付け外しを行うというところです。

モジュールを複数設置しても問題なく動かすにはブロックを分ける必要があります。

 const tabList = e.currentTarget.closest('.tab__list')

このように記述してある箇所を

 const tabList = document.querySelectorAll('.tab__list')

上記のように記述すると、モジュールを複数設置した場合に、
全てのモジュールの「.tab__list」が取得されてしまい、想定と異なる動きになってしまいます。


続いて、イベントが発生したtabと同階層にある全てのtab、
及びそれに紐づく全てのcontentから一旦activeクラスを外します。

それから、イベントが発生したtabにのみactiveクラスを付与します。
これで表示されているtabにのみ色をつけることができます。

//クリックされたtabと同階層のtab及びcontentからactiveクラスを削除
    tabItems.forEach((tabItem) => {
      tabItem.classList.remove('is-active')
    })
    tabContentItems.forEach((tabContentItem) => {
      tabContentItem.classList.remove('is-active')
    })
//クリックされたtabにactiveクラスを付与
    e.currentTarget.classList.add('is-active')

最後に、イベントが発生したtabが持つカスタムデータ属性の値と
等しい値を持つcontentを表示させることで完成です。

//取得したtabのデータ属性の値と等しい値を持つコンテンツにactiveクラスを付与
    tabContentItems.forEach((tabContentItem) => {
      if (tabContentItem.dataset.content === tabTargetData) {
        tabContentItem.classList.add('is-active')
      }
    })

HTMLのポイント

tab部分はクリックできることを明示的に示すためにbuttonタグで実装しています。

さらに今回のロジックで重要になるカスタムデータ属性をtabと本文に設定します。
developer.mozilla.org

<div class="tab">
 <ul class="tab__list">
  <li class="tab__item is-active" data-tab="01">
   <button class="tab__text" type="button">tab01</button>
...途中省略
</div>
<div class="tab__content">
 <div class="tab__content-item is-active" data-content="01">
  <p class="tab__content-text">tab01の本文です。</p>
 </div>
...途中省略
</div>

終わりに

今回はtabとcontentの紐付けにカスタムデータ属性を使用しました。
また、モジュールを複数設置できるように実装を行いました。

ただ、アクセシビリティーの観点から見ると改善の余地があるので
次回はこのコードを元にWAI-ARIAに対応させていきたいと思います。

今回は以上になります!


↓↓私の師匠、もりけんさんの武骨日記。問題集、要チェック

kenjimorita.jp