JavaScript

素のJavaScriptでタブ切り替え(タブパネル作成)

タブパネルを素のJavaScriptを使って(jQueryなしで)作成する方法についてご紹介します。

できるだけHTMLの仕様に基づいたものとしています。JavaScriptはクリックしたときの動作のためだけに用いていて、ほとんどはCSSで実現している形です。

デモ/タブパネルとは

タブパネルは、次のデモのようにタブで中身を切り替えられるユーザーインタフェースのことです。

タブAの中身

タブBの中身

タブCの中身

タブパネルのメリット

タブパネルのメリットは、ユーザーが同じ位置で中身を切り替えて見ることができるという点です。例えばコンテンツA、コンテンツB、コンテンツCをそれぞれ縦に並べると、見比べるときには、上下に移動しなければなりませんが、タブパネルの場合、移動が必要なくなります。

直感的にわかりやすいというのもあるのと、リンクでページを切り替える場合に比べると、表示の切り替わりが早いというのもあります。

タブパネルのデメリット

選択されているタブ以外は、CSSのdisplay: noneで非表示にしますので、デフォルトで非表示になっているコンテンツは、GoogleがSEO上評価してくれないというリスクがあります。

ソースコードと解説

HTML

<div class="tab-wrapper" role="tablist">
   <div class="tab">
      <input id="tab-a" type="radio" name="tab-radio" role="tab" class="tab-input" value="usage" checked="">
      <h2 class="tab-label-heading selected"><label class="tab-label" for="tab-a">タブA</label></h2>
   </div>
   <div class="tab">
      <input id="tab-b" type="radio" name="tab-radio"  role="tab" class="tab-input" value="type">
      <h2 class="tab-label-heading"><label class="tab-label" for="tab-b">タブB</label></h2>
   </div>
   <div class="tab">
      <input id="tab-c" type="radio" name="tab-radio"  role="tab" class="tab-input" value="maker">
      <h2 class="tab-label-heading"><label class="tab-label" for="tab-c">タブC</label></h2>
   </div>
</div>
<div class="tab-panel panel-a" role="tabpanel">
   <div><p>タブAの中身</p></div>
</div>
<div class="tab-panel panel-b" role="tabpanel">
   <div><p>タブBの中身</p></div>
</div>
<div class="tab-panel panel-c" role="tabpanel">
   <div><p>タブCの中身</p></div>
</div>

タブ部分にラジオボタンを用いているのはHTMLの意味論、アクセシビリティのことをできるだけ考えてのことです。JavaScriptを用いた本実装の場合、例えばdiv要素だけを使っても見た目と動作的には変わらないものができますが、クリックしたら択一式で選択される、という機能を果たすものとしてラジオボタンがHTMLでは用意されているので、それを用いるのが仕様に対しては素直だと考えます。どのくらい、この配慮が意味をなしているかはよくわかっていません。

role="tablist", role="tab", role="tabpanel"は、アクセシビリティのためのWAI-ARIAで定められている仕様で、タブであることを支援技術に伝えることができます。aria-selected="true"という、どのタブが選択された状態であるかを伝えるための仕様も定められているのですが、ラジオボタンを用いていればそこまでは不要です。

また、タブに見出し要素を用いているのも、文書構造への配慮です。タブパネルの中に見出しを入れるという実装も見かけることがありますが、タブ部分と同じ文言を書くことになり冗長になるので、このようにタブに見出し要素を使った方がシンプルになると思います。

タブパネル部分は、div要素を3つ並べて、選択されてないパネルはCSSで非表示にしています。

CSS

/*タブを横並びに*/
.tab-wrapper{
   display: flex;
}
.tab:not(:last-of-type) {
   margin-right: 2px;
}
/*ラジオボタンを全て非表示に*/
input[name="tab-radio"] {
   display: none;
}
.tab-label {
   display: block;
}
.tab-label{
   background-color: #b4bdbf;
   color: #525252;
   cursor: pointer;
   font-size: 16px;
   padding: 10px 20px;
   transition: .3s;
}
.tab-label:hover{
   opacity: .7;
}
/* 選択されたタブの見た目 */
.selected&gt;.tab-label{
   color: #000;
   background-color: #e2e8eb;
   transition: .3s;
}
/*タブパネル*/
.tab-panel{
   background-color: #e2e8eb;
   height: 170px;
}
.tab-panel&gt;div{
   display: flex;
   height: 100%;
   align-items: center;
   justify-content: center;
}
/*タブパネルの初期状態として選ばれてないものを非表示に*/
.panel-b,
.panel-c{
   display: none;
}

JavaScript

const tab_elements = document.getElementsByName('tab-radio');
const tab_panel_a = document.querySelector('.panel-a');
const tab_panel_b = document.querySelector('.panel-b');
const tab_panel_c = document.querySelector('.panel-c');

tab_elements.forEach( tab_element =&gt; {
   tab_element.addEventListener('click', function(){
      if (tab_element.id == 'tab-a'){
         tab_panel_a.style.display = 'block';
         tab_panel_b.style.display = 'none';
         tab_panel_c.style.display = 'none';
      }else if (tab_element.id == 'tab-b'){
         tab_panel_a.style.display = 'none';
         tab_panel_b.style.display = 'block';
         tab_panel_c.style.display = 'none';
      }else if (tab_element.id == 'tab-c'){
         tab_panel_a.style.display = 'none';
         tab_panel_b.style.display = 'none';
         tab_panel_c.style.display = 'block';
      }
      // 選択されたかどうかを示すクラス名「selected」の付与と削除
      tab_elements.forEach( tab_element =&gt; {
         tab_element.nextElementSibling.classList.remove('selected');
      });
      tab_element.nextElementSibling.classList.add('selected');
   });
});

ラジオボタンをクリックしたら紐づけられた見出しにクラス名selectedをつけるということをしています。ラジオボタンはCSSで非表示にしているためクリッカブルな部分がないように思われるかもしれませんが、label要素とidによってHTMLで紐づけているので、この場合、label要素をクリックしてもラジオボタンがクリックされたものと認識して動作するというのがJavaScriptの仕様です。

nextElementSiblingは、隣接する次の要素を取得するプロパティです。ラジオボタンの次にh2があるので、そのh2selectedのクラス名が付与されます。

まとめ

できるだけHTMLの仕様に基づいて、JavaScriptを使ってタブパネルを作る方法をご説明しました。JavaScriptはクリックしたときの動作のためだけに用いていて、ほとんどはCSSで実現している形です。JavaScriptでの実装と比べてHTMLの構造に制約があるものの、CSSだけで実装することも実はできるので、ご紹介しています。

著者のイメージ画像

株式会社BringFlower
稲田 高洋(Takahiro Inada)

2003年から大手総合電機メーカーでUXデザインプロセスの研究、実践。UXデザイン専門家の育成プログラム開発。SEOにおいても重要なW3Cが定めるWeb標準仕様策定にウェブアクセシビリティの専門家として関わる。2010~2018年に人間中心設計専門家を保有、数年間ウェブアクセシビリティ基盤委員も務める。その後、不動産会社向けにSaaSを提供する企業の事業開発部で複数サービスを企画、ローンチ。CMSを提供し1000以上のサイトを分析。顧客サポート、サイト運営にも関わる。
2022年3月に独立後、2024年4月に株式会社BringFlowerを設立。SEOコンサルを活動の軸に据えつつ、AIライティングツールの開発と運営を自ら行う。グッドデザイン賞4件、ドイツユニバーサルデザイン賞2件、米国IDEA賞1件の受賞歴あり。