JavaScriptのfor文で中身がふわっと表示されるタブ切り替えを実装してみる(パターン1)(備忘録)

今回は、jQueryを使用せずに、JavaScriptのfor文をうまく活用して中身がふわっと表示される、タブ切り替えを実装してみることにした。

大抵紹介されているのは、中身がdisplay:block;とdisplay:none;で表示非表示にさせるパターンが多いが、個人的にはいきなり消えたり現れたりするのは違和感があったため、cssを使ってコントロールしてみることにした。

では実装に入っていきたいと思う。

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>
          </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の内容 -->
          </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>

今回はタブが3つあるパターンで実装している。それぞれのタブを押すと該当する中身が表示されるようになっている。

<!-- タブ切り替え -->
<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>
  </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の内容 -->
  </div>
</div>
<!-- //タブ切り替え -->

全体をで囲んでいるが、こちらの記述は特になくてもOKだ。今回は他の要素との余白の調整したかったので使っている。

tab01〜tab03のタブについて

まず、tab01〜tab03のタブの実装だ。こちらはリストを用いて作成した。aタグをつけているのは、本来はaタグはリンク先を示すために使われるのだが、このタブで考えた時に、リンク先には該当はしないが、タブをクリックした時に中身の情報がみれるという点では、到達点を示すという点でずれていないと思い、使用している。

タブのリストを作成したときに注意する点としては、ページを読み込んだ時に、初めから表示させるものを決めておくということだ。

今回は、tab01に対して、activeというクラスを設定しているため、ページを読み込んだ時は、tab01が表示された状態となる。tab02を表示させたいのであれば、activeのクラスをtab01から外して、tab02につければよい。

tab01〜tab03のタブの中身について

タブの中身については、で全体を囲み、その中にを3つ作って実装している。

ちなみに、今回の実装では、divを用いているが、ulやolを使って実装することも可能だ。その時のページの内容によって、htmlのマークアップに適したものを使用していくのがいいかと思う。

先ほどtab01のタブをactiveに設定したため、タブの中身についても、tab01 のタブの中身に対してと記述している。

これで準備完了だ。

cssの記述について

@charset "utf-8";

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

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

.bl_tabContainer + * {
  margin-top: 30px;
}
/* タブリスト */
.bl_tabList {
  display: flex;
  justify-content: space-between;
}
.bl_tabList li {
  width: 32%;
}
.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 {
  position: relative;
}
.bl_tabContent > div {
  background-color: #fff;
  padding: 20px;
  opacity: 0;
  visibility: hidden;
  transition: 0.3s;
  position: absolute;
  width: 100%;
  top: 0;
  left: 0;
}
.bl_tabContent > div.active {
  opacity: 1;
  visibility: visible;
  position: static;
}
.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;
  }
}

タブのリストや中身の作り方が、ソースを見ていただけるとわかるかと思うので、詳細は省略する。

.bl_tabList li a {
  display: flex;
  justify-content: center;
  align-items: center;
}

このaタグ部分の記述については、よく使うので覚えておくことをおすすめする。この実装により、上下左右中央に要素を配置することができるようになる。

タブの中身のcssについて

/* タブコンテンツ */
.bl_tabContent {
  position: relative;
}
.bl_tabContent > div {
  opacity: 0;
  visibility: hidden;
  transition: 0.3s;
  position: absolute;
  width: 100%;
  top: 0;
  left: 0;
}
.bl_tabContent > div.active {
  opacity: 1;
  visibility: visible;
  position: static;
}

タブの中身については、activeがついていない時は、要素が見えなくなるように設定し、activeがついているときは、要素が現れるように設定した。

transitionを効かせるためには、opacity: 0;とvisibility: hidden;を使って対応する必要があるが、これだけだと、透明になる前のコンテンツの位置が残った状態で透明になるので、タブを切り替えた時に、本来表示したいtab01〜tab03のすぐ下の場所に表示されなくなってしまう。

そこで、activeがついていないタブの中身に対しては、positionを用いて、要素がtab01〜tab03のすぐ下の位置に来るよう調整している。

一方、activeがついているタブの中身については、opacity: 1;とvisibility: visible;で表示させるよう設定し、position: static;で元のデフォルト表示に戻すように対応した。

JavaScriptの記述について

'use strict';

// タブ切り替え
document.addEventListener('DOMContentLoaded', function(){
  const tabList = document.querySelectorAll(".bl_tabList li a");
  const tabContent = document.querySelectorAll(".bl_tabContent > div");

  for (let i = 0; i < tabList.length; i++) {
    tabList[i].addEventListener("click", function (e) {
      e.preventDefault();
      for (let j = 0; j < tabList.length; j++) {
        tabList[j].classList.remove("active");
      }
      for (let j = 0; j < tabContent.length; j++) {
        tabContent[j].classList.remove("active");
      }
      this.classList.add("active");
      tabContent[i].classList.add("active");
    });
  }
});

// スムーススクロール
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"
      });
    });
  });
});

まず、タブリスト(tabList)とタブの中身(tabContent)で変数を定義する。

for文について

for (let i = 0; i < tabList.length; i++)の記述は決まり文句だと思っていただいてよい。

iという文字については、aでもbでもcでも問題はない。ただ、使う場合は現在使用されている3箇所のiの文字を全て変更したい文字に変えて使用する必要がある。一般的にはiが使われることが多い。

let i = 0;について

let i = 0;で変数を定義する。0としているのは配列を数え始めを0番目とするためである。

htmlではulでリストを作成した際、1番目、2番目…と数えていくが、javascriptでは0番目(htmlでは1番目)、1番目(htmlでは2番目)…と数えていく。

つまり、1ずれた状態でカウントしていくことになるので、この理解がまず必要になる。

i < tabList.length;について

tabList.length;でhtml側で記述している「.bl_tabList li a」の数がいくつあるのかを知ることができる。

<ul class="bl_tabList">
  <li><a href="" class="active">Tab01</a></li>
  <li><a href="">Tab02</a></li>
  <li><a href="">Tab03</a></li>
</ul>

上記のように、今回は3つあるため、数は3となる。上記のリストの数が増えれば、その数は都度変わっていくようになっている。

つまり、現在のソースから考えると、i < tabList.length;というのはi < 3といいうことになる。

++について

for (let i = 0; i < tabList.length; i++) {
  この中に処理したい内容を記述していく
}

i++は、上述のように「この中に処理したい内容を記述していく」の対応が終わったら、iの値を1増やして、もう一度「この中に処理したい内容を記述していく」を実行していくといったようなことだと認識していただければいいかと思う。

for (let i = 0; i < tabList.length; i++)のまとめ

言語化するとすれば、下記のようになるかと思う。

①配列iが0番目の時に、中身の処理を実行
②①の処理が終わったら、配列が1番目の時に、中身の処理を実行
③②の処理が終わったら、配列が2番目の時に、中身の処理を実行
④i < tabList.length;は3より小さいことを表しているため、配列3番目の時の処理は実行されない

このような流れで処理が実行される。

tabList[i].addEventListener(“click”, function (e) {}について

ハンバーガーメニューの時も似たような処理をしていたと思うが、簡単に言えば、tabListをクリックした時の処理が記述されている。似たようなところの解説は省略したいと思う。

今回はtab01〜tab03のいずれかのタブをクリックした時に、処理が走るようにしたいので、この記述にしている。

tabList[i]について

ここでfor文でも触れた配列を指定している。tabKistの後ろに[i]をおくことで、クリックした時に、何番目がクリックされたのかが判別できるようになる。

実際の処理内容について

tabList[i].addEventListener("click", function (e) {
  e.preventDefault();
  for (let j = 0; j < tabList.length; j++) {
    tabList[j].classList.remove("active");
  }
  for (let j = 0; j < tabContent.length; j++) {
    tabContent[j].classList.remove("active");
  }
  this.classList.add("active");
  tabContent[i].classList.add("active");
});

今回は、tab01〜tab03のいずれかをクリックした時に、aタグのページ遷移が解除され、全てのタブのリストと中身からactiveというクラスが削除され、その後、クリックしたタブのリストと中身にactiveというクラスが付与されるようにしている。

e.preventDefault();について

これはaタグをクリックした時にページ遷移しないようにするための記述である。

jを用いた2つのfor文の記述について

こちらはタブをクリックした時に、一旦全てのタブのリストと中身に対して、activeのクラスが削除されるように対応している。

jと定義している理由については、すでにiをfor文で定義しており、そのiの配列はクリックした時に使われているため、同じiを使ってしまうと、正しい処理が実行されなくなってしまうためだ。

ここで、tabListとtabContentのfor文でjが2回使われていて問題がないのかという疑問が生まれるかもしれないが、これはタブのリストと中身の数が同じであり、表示される内容も対になっている(tab01に対してtab01の内容が表示される)ため問題ない。

.classList.remove(“active”)の記述は、html上にactiveというクラスがあれば取り除くといった内容だと認識だ。今回はtabList[j]やtabContent[j]に対して使われている。

this.classList.add(“active”);とtabContent[i].classList.add(“active”);について

ここはそこまで難しくはないが、thisというのはtabList[i]を指している。ここでは一番外側で指定しているものがtabList[i]のためthisを用いて省略して記述することができるのだ。

javascriptでは毎回同じことを書くのが手間だったり、他の人がソースを見た時に見づらいなど、いろんな問題があるようで、できるだけ分かりやすいコードで記述していくことが求められるようだ。

そのために再利用しやすいコードに見直したりすることをリファクタリングというようだ。

今回のthisもそのリファクタリングのための記述だと思っていただければいいと思う。

.classList.add(“active”)については、先ほどのremoveと逆で、html上にactiveというクラスを付与することができる。今回はtabList[i]やtabContent[i]に対して使われている。

以上で終わりだが、スムーススクロールの記述は組み合わせて使うことが多いので、前回同様残している。解説は省略したいと思う。

次回もタブ切り替えシリーズの別パターン実装をやっていきたいと思う。

↑新しい記事を更新したので、気になる方は下記参照ください。

javascriptのforEachとdatasetを使って中身がふわっと表示されるタブ切り替えを実装してみる(パターン2)(備忘録)

2 返信

トラックバック & ピングバック

  1. […] javascriptのfor文で中身がふわっと表示されるタブ切り替えを実装してみる(パターン1)(備忘録) […]

コメントはクローズされています。