タブ切り替えの項目が多くなった時の対応方法(備忘録)

今回は、前回のjQueryを使用して中身がふわっと表示されるタブ切り替えのソースを流用して、タブの数が多くなった時にどのように対応していくかをまとめてみることにした。

前回の記事はこちら

下記にデモを2つ用意した。

1つ目は、スマホ幅の時にタブが多くなってしまった時に横にスクロールすることで、はみ出ている部分が表示できるようにしている。

2つ目は、カレンダーのようにhtmlソースが膨大な量になってしまった時に、インクルード化して読み込むことで、その月のカレンダーだけ編集して更新作業ができるようにしている。

それでは解説に入っていく。

demo01について

htmlの記述について

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>タブ切り替え</title>
  <link rel="stylesheet" href="../reset.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main>
    <section>
      <div class="ly_inner" id="menu1">
        <h2>menu1</h2>
        <!-- タブ切り替え -->
        <div class="bl_tabContainer">
          <ul class="bl_tabList">
            <li><a href="" class="active">Tab01</a></li>
            <li><a href="">Tab02</a></li>
            <li><a href="">Tab03</a></li>
            <li><a href="">Tab04</a></li>
            <li><a href="">Tab05</a></li>
            <li><a href="">Tab06</a></li>
            <li><a href="">Tab07</a></li>
            <li><a href="">Tab08</a></li>
            <li><a href="">Tab09</a></li>
            <li><a href="">Tab10</a></li>
          </ul>
          <div class="bl_tabContent">
            <!-- Tab01の内容 -->
            <div class="active">
              <p>Tab01の内容が入りますTab01の内容が入りますTab01の内容が入りますTab01の内容が入りますTab01の内容が入りますTab01の内容が入りますTab01の内容が入りますTab01の内容が入りますTab01の内容が入りますTab01の内容が入ります</p>
            </div>
            <!-- //Tab01の内容 -->
            <!-- Tab02の内容 -->
            <div>
              <p>Tab02の内容が入ります</p>
              <p>Tab02の内容が入ります</p>
              <p>Tab02の内容が入ります</p>
              <p>Tab02の内容が入ります</p>
              <p>Tab02の内容が入ります</p>
              <p>Tab02の内容が入ります</p>
              <p>Tab02の内容が入ります</p>
              <p>Tab02の内容が入ります</p>
            </div>
            <!-- //Tab02の内容 -->
            <!-- Tab03の内容 -->
            <div>
              <p>Tab03の内容が入ります</p>
            </div>
            <!-- //Tab03の内容 -->
            <!-- Tab04の内容 -->
            <div>
              <p>Tab04の内容が入ります</p>
            </div>
            <!-- //Tab04の内容 -->
            <!-- Tab05の内容 -->
            <div>
              <p>Tab05の内容が入ります</p>
            </div>
            <!-- //Tab05の内容 -->
            <!-- Tab06の内容 -->
            <div>
              <p>Tab06の内容が入ります</p>
            </div>
            <!-- //Tab06の内容 -->
            <!-- Tab07の内容 -->
            <div>
              <p>Tab07の内容が入ります</p>
            </div>
            <!-- //Tab07の内容 -->
            <!-- Tab08の内容 -->
            <div>
              <p>Tab08の内容が入ります</p>
            </div>
            <!-- //Tab08の内容 -->
            <!-- Tab09の内容 -->
            <div>
              <p>Tab09の内容が入ります</p>
            </div>
            <!-- //Tab09の内容 -->
            <!-- Tab10の内容 -->
            <div>
              <p>Tab10の内容が入ります</p>
            </div>
            <!-- //Tab10の内容 -->
          </div>
        </div>
        <!-- //タブ切り替え -->
        <div class="md_textblock">
          <p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
          <p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
          <p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
          <p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
          <p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
        </div>
      </div>
    </section>

  </main>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
  <script src="style.js"></script>
</body>
</html>

htmlのソースは前回とほぼ変わっていない。タブの数を増やしただけだ。解説は省略するので、詳しく知りたければ下記の記事を参考にしてほしい。

前回の記事はこちら

cssの記述について

@charset "utf-8";

/* ==========================
  初期設定
========================== */
*,
*::before,
*::after {
  box-sizing: border-box;
}
img {
  width: 100%;
}

/* ==========================
  タブ
========================== */

.bl_tabContainer + * {
  margin-top: 30px;
}
/* タブリスト */
.bl_tabList {
  display: flex;
  overflow-x: scroll;
  -ms-overflow-style: none;
  scrollbar-width: none;
}
.bl_tabList::-webkit-scrollbar {
  display: none;
}
.bl_tabList li {
  margin-right: 5px;
}
.bl_tabList li:last-of-type {
  margin-right: 0;
}
.bl_tabList li a {
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: #aaa;
  padding: 20px 10px;
  transition: 0.3s;
}
.bl_tabList li a.active {
  background-color: #fff;
  cursor: text;
}
/* タブコンテンツ */
.bl_tabContent > div {
  background-color: #fff;
  padding: 20px;
  display: none;
}
.bl_tabContent > div.active {
  display: block;
}
.bl_tabContent > div > * + * {
  margin-top: 10px;
}

/* ==========================
  タブ以外のコンテンツの中身
========================== */
.ly_inner {
  width: 100%;
  max-width: 1080px;
  margin: 100px auto;
  padding: 30px;
  background-color: #ccc;
}
.ly_inner h2 {
  font-size: 150%;
  font-weight: bold;
  margin-bottom: 30px;
}
.md_textblock > * + * {
  margin-top: 10px;
}

/* ====================================
  ここからPC幅
==================================== */
@media screen and (min-width: 768px) {
  .hp_displaySP {
    display: none !important;
  }
  .bl_tabList li a:not(.active):hover {
    background-color: #fff;
  }
}

/* ====================================
  ここからスマホ幅
==================================== */
@media screen and (max-width: 767px) {
  .hp_displayPC {
    display: none !important;
  }
}

こちらも前回の記事とほとんど一緒であるが、overflow-x: scroll;という記述を追加している。

overflow-x: scroll;によって、ブロックレベル要素の内容が左右の境界からはみ出る場合に、スクロールさせて表示させることができる。

overflow-xのxは横方向、もしyなら縦方向となるのだが、これは中学生の数学の時に出てきた、一次関数のx軸とy軸をイメージすると迷わなくなるかと思う。

あとは、スクロールした時のバーを削除するために、scrollbar-width: none;を入れている。

このスクロールバーは、今回はいらないと思ったので削除しているが、別に削除しなくても問題がない場合もあるので状況によって判断するのがいいだろう。

javascriptの記述について

'use strict';

// タブ切り替え
$(function(){
  $('.bl_tabList li a').click(function (e) {
    e.preventDefault();
    $('.bl_tabList li a').removeClass('active');
    $('.bl_tabContent > div').removeClass('active');
    $(this).addClass('active');
    const array = $('.bl_tabList li a').index(this);
    $('.bl_tabContent > div').eq(array).fadeIn(300).addClass('active');
    $('.bl_tabContent > div').not('.active').hide();
  });
});

// スムーススクロール
window.addEventListener('DOMContentLoaded', function () {
  const anchorLinks = document.querySelectorAll('a[href^="#"]');
  const anchorLinksArr = Array.prototype.slice.call(anchorLinks);

  anchorLinksArr.forEach(function (link) {
    link.addEventListener('click', function (e) {
      e.preventDefault();
      const targetId = link.hash;
      const targetElement = document.querySelector(targetId);
      const targetOffsetTop = window.pageYOffset + targetElement.getBoundingClientRect().top; //ここに- 50 などと数値を入れるとヘッダー固定のスクロールが実現できる
      window.scrollTo({
        top: targetOffsetTop,
        behavior: "smooth"
      });
    });
  });
});

前回と同じのため解説は省略する。

前回の記事はこちら

demo02について

htmlの記述について

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>タブ切り替え</title>
  <link rel="stylesheet" href="../reset.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main>
    <section>
      <div class="ly_inner" id="menu1">
        <h2>カレンダー</h2>
        <!-- タブ切り替え -->
        <div class="bl_tabContainer">
          <ul class="bl_tabList">
            <li><a href="" class="active">1月</a></li>
            <li><a href="">2月</a></li>
            <li><a href="">3月</a></li>
            <li><a href="">4月</a></li>
            <li><a href="">5月</a></li>
            <li><a href="">6月</a></li>
            <li><a href="">7月</a></li>
            <li><a href="">8月</a></li>
            <li><a href="">9月</a></li>
            <li><a href="">10月</a></li>
            <li><a href="">11月</a></li>
            <li><a href="">12月</a></li>
          </ul>
          <div class="bl_tabContent">
            <!-- Tab01の内容 -->
            <div class="calendar01 active">
            </div>
            <!-- //Tab01の内容 -->
            <!-- Tab02の内容 -->
            <div class="calendar02">
            </div>
            <!-- //Tab02の内容 -->
            <!-- Tab03の内容 -->
            <div class="calendar03">
            </div>
            <!-- //Tab03の内容 -->
            <!-- Tab04の内容 -->
            <div class="calendar04">
            </div>
            <!-- //Tab04の内容 -->
            <!-- Tab05の内容 -->
            <div class="calendar05">
            </div>
            <!-- //Tab05の内容 -->
            <!-- Tab06の内容 -->
            <div class="calendar06">
            </div>
            <!-- //Tab06の内容 -->
            <!-- Tab07の内容 -->
            <div class="calendar07">
            </div>
            <!-- //Tab07の内容 -->
            <!-- Tab08の内容 -->
            <div class="calendar08">
            </div>
            <!-- //Tab08の内容 -->
            <!-- Tab09の内容 -->
            <div class="calendar09">
            </div>
            <!-- //Tab09の内容 -->
            <!-- Tab10の内容 -->
            <div class="calendar10">
              <p>Tab10の内容が入ります</p>
            </div>
            <!-- //Tab10の内容 -->
            <!-- Tab11の内容 -->
            <div class="calendar11">
              <p>Tab11の内容が入ります</p>
            </div>
            <!-- //Tab11の内容 -->
            <!-- Tab12の内容 -->
            <div class="calendar12">
              <p>Tab12の内容が入ります</p>
            </div>
            <!-- //Tab12の内容 -->
          </div>
        </div>
        <!-- //タブ切り替え -->
        <div class="md_textblock">
          <p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
          <p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
          <p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
          <p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
          <p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
        </div>
      </div>
    </section>

  </main>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
  <script src="style.js"></script>
</body>
</html>

カレンダーを実装させるために、タブのボタン部分は1月〜12月分を作成し、中身の部分については、

インクルードで別のhtmlファイルを読み込ませて対応させるため、下記の記述にしている。

<!-- Tab01の内容 -->
<div class="calendar01 active">
</div>
<!-- //Tab01の内容 -->
<!-- Tab02の内容 -->
<div class="calendar02">
</div>
<!-- //Tab02の内容 -->

↑以降12月まで続く

カレンダーの中身については、別のhtmlから読み込ませる関係で、incフォルダを作ってその中に、1月〜12月分を作成している。

1月のhtml記述例(calendar01.html)

<table class="bl_calendar">
  <tr>
    <th>日</th>
    <th>月</th>
    <th>火</th>
    <th>水</th>
    <th>木</th>
    <th>金</th>
    <th>土</th>
  </tr>
  <tr>
    <td></td>
    <td></td>
    <td></td>
    <td></td>
    <td></td>
    <td>1</td>
    <td>2</td>
  </tr>
  <tr>
    <td>3</td>
    <td>4</td>
    <td>5</td>
    <td>6</td>
    <td>7</td>
    <td>8</td>
    <td>9</td>
  </tr>
  <tr>
    <td>10</td>
    <td>11</td>
    <td>12</td>
    <td>13</td>
    <td>14</td>
    <td>15</td>
    <td>16</td>
  </tr>
  <tr>
    <td>17</td>
    <td>18</td>
    <td>19</td>
    <td>20</td>
    <td>21</td>
    <td>22</td>
    <td>23</td>
  </tr>
  <tr>
    <td>24</td>
    <td>25</td>
    <td>26</td>
    <td>27</td>
    <td>28</td>
    <td>29</td>
    <td>30</td>
  </tr>
  <tr>
    <td>31</td>
    <td></td>
    <td></td>
    <td></td>
    <td></td>
    <td></td>
    <td></td>
  </tr>
</table>

これと同じように、2月以降も別ファイルで作成していく。

今回は1月と2月のソースしか準備していないため、順番にクリックしていけば交互に表示が確認できるようになっている。

cssの記述について

@charset "utf-8";

/* ==========================
  初期設定
========================== */
*,
*::before,
*::after {
  box-sizing: border-box;
}
img {
  width: 100%;
}

/* ==========================
  タブ
========================== */

.bl_tabContainer + * {
  margin-top: 30px;
}
/* タブリスト */
.bl_tabList {
  display: flex;
  flex-wrap: wrap;
  margin-bottom: 20px;
  justify-content: space-between;
}
.bl_tabList li a {
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: #aaa;
  padding: 10px;
  transition: 0.3s;
}
.bl_tabList li a.active {
  background-color: #fff;
  cursor: text;
}
/* タブコンテンツ */
.bl_tabContent > div {
  background-color: #fff;
  padding: 20px;
  display: none;
}
.bl_tabContent > div.active {
  display: block;
}
.bl_tabContent > div > * + * {
  margin-top: 10px;
}

/* ==========================
  タブ以外のコンテンツの中身
========================== */
.ly_inner {
  width: 100%;
  max-width: 1080px;
  margin: 100px auto;
  padding: 30px;
  background-color: #ccc;
}
.ly_inner h2 {
  font-size: 150%;
  font-weight: bold;
  margin-bottom: 30px;
}
.md_textblock > * + * {
  margin-top: 10px;
}

/* テーブル */
.bl_calendar {
  border: 1px solid #ccc;
  margin: 0 auto;
}
.bl_calendar tr {
  border-bottom: 1px solid #ccc;
}
.bl_calendar tr:last-of-type {
  border-bottom: none;
}
.bl_calendar tr td {
  border-right: 1px solid #ccc;
  padding: 5px;
  text-align: center;
}
.bl_calendar tr th {
  border-right: 1px solid #fff;
  padding: 5px;
  text-align: center;
}
.bl_calendar tr th {
  background-color: #ccc;
}
.bl_calendar tr th:last-child,
.bl_calendar tr td:last-child {
  border-right: none;
}

/* ====================================
  ここからPC幅
==================================== */
@media screen and (min-width: 768px) {
  .hp_displaySP {
    display: none !important;
  }
  .bl_tabList li a:not(.active):hover {
    background-color: #fff;
  }
  .bl_tabList li {
    width: 15%;
  }
  .bl_tabList li:nth-of-type(n + 7) {
    margin-top: 10px;
  }
}

/* ====================================
  ここからスマホ幅
==================================== */
@media screen and (max-width: 767px) {
  .hp_displayPC {
    display: none !important;
  }
  .bl_tabList li {
    width: 24%;
    font-size: 0.8rem;
  }
  .bl_tabList li:nth-of-type(n + 5) {
    margin-top: 5px;
  }
  .bl_tabList li a {
    padding: 5px;
  }
  .bl_calendar {
    word-break: break-all;
    table-layout: fixed;
    width: 100%;
  }
}

こちらも前回とほぼ同じだ。タブボタンとタブの中身のテーブルの見た目を調整した程度である。

タブのボタンデザインを、タブの中身の上部とくっついた状態ではなく、離してボタン風に見せたかったので、cssで調整した。

カレンダーの場合、ボタンの数が12必要になるので、あまり大きく見せるのもよくなさそうなので、シンプルにしてみた。

また、タブの中身のカレンダーについては、tableを用いて作成している。tableの解説はググればたくさん出てくるので、今回は省略する。

javascriptの記述について

'use strict';

// タブ切り替え
$(function(){
  $('.bl_tabList li a').click(function (e) {
    e.preventDefault();
    $('.bl_tabList li a').removeClass('active');
    $('.bl_tabContent > div').removeClass('active');
    $(this).addClass('active');
    const array = $('.bl_tabList li a').index(this);
    $('.bl_tabContent > div').eq(array).fadeIn(300).addClass('active');
    $('.bl_tabContent > div').not('.active').hide();
  });
});

// スムーススクロール
window.addEventListener('DOMContentLoaded', function () {
  const anchorLinks = document.querySelectorAll('a[href^="#"]');
  const anchorLinksArr = Array.prototype.slice.call(anchorLinks);

  anchorLinksArr.forEach(function (link) {
    link.addEventListener('click', function (e) {
      e.preventDefault();
      const targetId = link.hash;
      const targetElement = document.querySelector(targetId);
      const targetOffsetTop = window.pageYOffset + targetElement.getBoundingClientRect().top; //ここに- 50 などと数値を入れるとヘッダー固定のスクロールが実現できる
      window.scrollTo({
        top: targetOffsetTop,
        behavior: "smooth"
      });
    });
  });
});

// インクルード
fetch("inc/calendar01.html")
  .then(response => {
    return response.text()
  })
  .then(data => {
    document.querySelector(".calendar01").innerHTML = data;
  });
fetch("inc/calendar02.html")
  .then(response => {
    return response.text()
  })
  .then(data => {
    document.querySelector(".calendar02").innerHTML = data;
  });
fetch("inc/calendar03.html")
  .then(response => {
    return response.text()
  })
  .then(data => {
    document.querySelector(".calendar03").innerHTML = data;
  });
fetch("inc/calendar04.html")
  .then(response => {
    return response.text()
  })
  .then(data => {
    document.querySelector(".calendar04").innerHTML = data;
  });
fetch("inc/calendar05.html")
  .then(response => {
    return response.text()
  })
  .then(data => {
    document.querySelector(".calendar05").innerHTML = data;
  });
  fetch("inc/calendar06.html")
  .then(response => {
    return response.text()
  })
  .then(data => {
    document.querySelector(".calendar06").innerHTML = data;
  });
  fetch("inc/calendar07.html")
  .then(response => {
    return response.text()
  })
  .then(data => {
    document.querySelector(".calendar07").innerHTML = data;
  });
  fetch("inc/calendar08.html")
  .then(response => {
    return response.text()
  })
  .then(data => {
    document.querySelector(".calendar08").innerHTML = data;
  });
  fetch("inc/calendar09.html")
  .then(response => {
    return response.text()
  })
  .then(data => {
    document.querySelector(".calendar09").innerHTML = data;
  });
  fetch("inc/calendar10.html")
  .then(response => {
    return response.text()
  })
  .then(data => {
    document.querySelector(".calendar10").innerHTML = data;
  });
  fetch("inc/calendar11.html")
  .then(response => {
    return response.text()
  })
  .then(data => {
    document.querySelector(".calendar11").innerHTML = data;
  });
  fetch("inc/calendar12.html")
    .then(response => {
      return response.text()
    })
    .then(data => {
      document.querySelector(".calendar12").innerHTML = data;
    });

インクルードの記述部分が追加されている。

fetch("inc/calendar01.html")
  .then(response => {
    return response.text()
  })
  .then(data => {
    document.querySelector(".calendar01").innerHTML = data;
  });

まず、fetchの「inc/calendar01.html」部分に取得したいパスを記載し、これに対し、thenで繋げて記述していく。

textにしているのはリクエストの結果を文字列で得られるようにするためだ。responseという記述は決まり文句だと思っていただくのがいいだろう。この記事では解説すると長くなってしまうので省略したい。

2つ目のthenでindex.html上に取得したいクラスcalendar01を指定することで、その中身に対して「nc/calendar01.html」のソースが置き換わるのだ。

これで該当のカレンダー月の中身が表示できるようになる。

以上で終わりだが、別にfetchでインクルード対応しなくても、jQueryを使うともっと簡単にできるので、試してみたい方は以前紹介した記事のヘッダー部分のインクルードを参考にして対応してみるといいと思う。

ハンバーガーメニューを展開してからのメニューをさらに展開させるメニューをjQueryで作ってみた。2階層パターン対応(備忘録)