🕌

VanillaJS(jQueryを使わない)でスライダーを実装する -HTML, CSS, JavaScript-

2024/03/25に公開

はじめに

VanillaJSとは、実は特定のライブラリやフレームワークを使用せず、純粋なJavaScriptのコードを使ってWeb開発を行うことを意味します。
最近ではjQueryやその他フレームワークを使用したものでもJavaScriptと呼ばれてしまっている記事もあるので、区別をするためここではVanillaJSという単語を使用してます。
この記事では、HTML, CSS, そしてVanillaJSを使って、基本的なイメージスライダーを作成する方法を紹介します。

完成イメージ


画像3枚が数秒ごとにスライドしていきます。

コード

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="./css/styles.css">
  <title>Sample_Slider</title>
</head>
<body>
  <section class="slider">
    <!-- スライダー部分 -->
    <div class="slider__track active__slide">
      <div class="slider__slide">
        <img src="images/main1.webp" alt="スライド1" class="slider__image" width="0" height="0" decoding="async">
      </div>
      <div class="slider__slide">
        <img src="images/main2.webp" alt="スライド2" class="slider__image" width="0" height="0" decoding="async">
      </div>
      <div class="slider__slide">
        <img src="images/main3.webp" alt="スライド3" class="slider__image" width="0" height="0" decoding="async">
      </div>
    </div>
    <!-- ページネーション部分 -->
    <div class="slider-pagination">
      <div class="pagination-item active"></div>
      <div class="pagination-item"></div>
      <div class="pagination-item"></div>
    </div>
  </section>

  <script src="./js/main.js" defer></script>
</body>
</html>
styles.css
body {
  margin: 0;
  padding: 0;
  .slider {
    position: relative;
    width: 100%;
    overflow: hidden;
    .slider__track {
      display: flex;
      transition: transform 0.7s ease;
      /* 各スライドが全体の1/3の幅を持つように調整 */
      width: 300%; /* ※スライドの枚数を変える場合は修正 */
      .slider__slide {
        width: 100%;
        height: 100vh;
        position: relative;
        overflow: hidden;
        .slider__image {
          width: 100%;
          height: 100%;
          object-fit: cover;
        }
        /* 画像のサイズによって表示する位置を調整してください */
        &:nth-child(1) > .slider__image {
          object-position: center;
        }
        &:nth-child(2) > .slider__image {
          object-position: left top;
        }
        &:nth-child(3) > .slider__image {
          object-position: center;
        }
      }
    }
    /* ※スライドの枚数を変える場合は修正 */
    .slider__track.active__slide1 {
      transform: translateX(0%);
    }
    .slider__track.active__slide2 {
      transform: translateX(-33.3333%);
    }
    .slider__track.active__slide3 {
      transform: translateX(-66.6667%);
    }
    /* ページネーション */
    .slider-pagination {
      position: absolute;
      bottom: 20px;
      left: 50%;
      transform: translateX(-50%);
      display: flex;
      .pagination-item {
        width: 10px;
        height: 10px;
        background-color: rgba(0, 0, 0, 0.7);
        border-radius: 50%;
        margin: 0 5px;
      }
      .pagination-item.active {
        background-color: #fea617;
      }
    }
  }
}
main.js
document.addEventListener('DOMContentLoaded', () => {
  const sliderTrack = document.querySelector('.slider__track');
  const slides = Array.from(sliderTrack.children);
  const paginationItems = document.querySelectorAll('.pagination-item');
  let currentSlide = 0;

  // スライドのクラスを更新する関数
  const updateSlideClass = () => {
     // `sliderTrack`のクラス名を調べ、`active__slide`で始まるものを削除
    sliderTrack.className = sliderTrack.className.split(' ').filter(className => !className.startsWith('active__slide')).join(' ');

  // 新しいクラスを追加
  sliderTrack.classList.add(`active__slide${currentSlide + 1}`);
  };

  // ページネーションアイテムの状態を更新する関数
  const updatePagination = () => {
    paginationItems.forEach((item, index) => {
      item.classList.toggle('active', index === currentSlide);
    });
  };

  // スライドを切り替える関数
  const switchSlide = (manualIndex) => {
    if (manualIndex !== undefined) {
      currentSlide = manualIndex;
    } else {
      // 次のスライドへ移動
      currentSlide = (currentSlide + 1) % slides.length;
    }
    updateSlideClass();
    // ページネーションアイテムの状態を更新
    updatePagination();
  };

  // ページネーションアイテムをクリックした時の処理
  paginationItems.forEach((item, index) => {
    item.addEventListener('click', () => {
      switchSlide(index);
    });
  });

  // スライダーの切り替え
  setInterval(() => switchSlide(), 6000);

});

コードの補足

コードの説明は適時コメント入れてある通りですが、画像の枚数を修正する場合はCSSの下記の箇所を修正していただく必要があります。

スライドの画像全てを横並びにした幅を指定します。なので、100% * Nの値に修正してください。
.slider__track {
  width: 300%;
}
横並びにした画像を1枚ずつ表示します。なので、.slider .slider__track.activeを追加して100% / Nの値を順に指定するようにしてください。
.slider .slider__track.active__slide1 {
  transform: translateX(0%);
}
.slider .slider__track.active__slide2 {
  transform: translateX(-33.3333%);
}
.slider .slider__track.active__slide3 {
  transform: translateX(-66.6667%);
}

おまけ

現状このコードを違うプロジェクトで使い回そうとして画像の枚数が変わる場合、HTMLのSlide要素とページネーションを追加し、CSSも修正しないといけません。
動的に変わる箇所はJavaScript内で実装し、スライダーの要素を追加しただけで動作するコードに修正します。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="./css/styles.css">
  <title>Sample_Slider</title>
</head>
<body>
  <section class="slider">
    <!-- スライダー部分 -->
    <div class="slider__track active__slide">
      <div class="slider__slide">
        <img src="images/main1.webp" alt="スライド1" class="slider__image" width="0" height="0" decoding="async">
      </div>
      <div class="slider__slide">
        <img src="images/main2.webp" alt="スライド2" class="slider__image" width="0" height="0" decoding="async">
      </div>
      <div class="slider__slide">
        <img src="images/main3.webp" alt="スライド3" class="slider__image" width="0" height="0" decoding="async">
      </div>
      <div class="slider__slide">
        <img src="images/main4.webp" alt="スライド3" class="slider__image" width="0" height="0" decoding="async">
      </div>
    </div>
    <!-- ページネーション部分 -->
    <div class="slider-pagination">
      <!-- コードを記載してた箇所 -->
    </div>
  </section>

  <script src="./js/main.js" defer></script>
</body>
</html>

styles.css
body {
  margin: 0;
  padding: 0;
  .slider {
    position: relative;
    width: 100%;
    overflow: hidden;
    .slider__track {
      display: flex;
      transition: transform 0.7s ease;
      /* 各スライドが全体の1/3の幅を持つように調整 */
      width: 400%; /* ※スライドの枚数を変える場合は修正 */
      .slider__slide {
        width: 100%;
        height: 100vh;
        position: relative;
        overflow: hidden;
        .slider__image {
          width: 100%;
          height: 100%;
          object-fit: cover;
        }
        /* 画像のサイズによって表示する位置を調整してください */
        &:nth-child(1) > .slider__image {
          object-position: center;
        }
        &:nth-child(2) > .slider__image {
          object-position: left top;
        }
        &:nth-child(3) > .slider__image {
          object-position: center;
        }
      }
    }
    /* ページネーション */
    .slider-pagination {
      position: absolute;
      bottom: 20px;
      left: 50%;
      transform: translateX(-50%);
      display: flex;
      .pagination-item {
        width: 10px;
        height: 10px;
        background-color: rgba(0, 0, 0, 0.7);
        border-radius: 50%;
        margin: 0 5px;
      }
      .pagination-item.active {
        background-color: #fea617;
      }
    }
  }
}
main.js
document.addEventListener('DOMContentLoaded', () => {
  const sliderTrack = document.querySelector('.slider__track');
  const slides = Array.from(sliderTrack.children);
  const sliderPagination = document.querySelector('.slider-pagination');
  let currentSlide = 0;

  // ページネーション要素を動的に生成
  const createPaginationItems = () => {
    slides.forEach((_, index) => {
      const paginationItem = document.createElement('div');
      paginationItem.classList.add('pagination-item');
      if (index === 0) paginationItem.classList.add('active'); // 最初のアイテムにはactiveクラスを追加
      sliderPagination.appendChild(paginationItem);
    });
  };

  // スライドのクラスと位置を更新する関数
  const updateSlidePosition = () => {
    const translateValue = -(100 / slides.length) * currentSlide;
    sliderTrack.style.transform = `translateX(${translateValue}%)`;
  };

  // ページネーションアイテムの状態を更新する関数
  const updatePagination = () => {
    const paginationItems = document.querySelectorAll('.pagination-item');
    paginationItems.forEach((item, index) => {
      item.classList.toggle('active', index === currentSlide);
    });
  };

  // スライドを切り替える関数
  const switchSlide = (manualIndex) => {
    if (manualIndex !== undefined) {
      currentSlide = manualIndex;
    } else {
      // 次のスライドへ移動
      currentSlide = (currentSlide + 1) % slides.length;
    }
    updateSlidePosition();
    updatePagination();
  };

  // ページネーションアイテムをクリックした時の処理を設定
  const setupPaginationEventListeners = () => {
    const paginationItems = document.querySelectorAll('.pagination-item');
    paginationItems.forEach((item, index) => {
      item.addEventListener('click', () => {
        switchSlide(index);
      });
    });
  };

  // 初期化関数
  const initSlider = () => {
    createPaginationItems();
    setupPaginationEventListeners();
    setInterval(() => switchSlide(), 6000);
  };

  // スライダー初期化
  initSlider();
});

上記に修正したことで、最小限の修正でコードを使いまわすことができます。

Discussion