JavaScriptのスライダーのコードの書き方

2021年3月4日JavaScript

JavaScriptのスライダーのコードの書き方を紹介します。

スライダーのデモ

See the Pen
スライダー
by Sosak (@Sosak2021)
on CodePen.

JavaScriptのスライダーのコードの書き方

HTML
      
//レビューやお客様の声
<div class="slider">
        <div class="slide slide--1">
          <div class="testimonial">
            <h5 class="testimonial__header">スライド1</h5>
            <blockquote class="testimonial__text">
              ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。
            </blockquote>
            <address class="testimonial__author">
              <img src="http://unsplash.it/50/50/?random" alt="" class="testimonial__photo" />
              <h6 class="testimonial__name">名前1</h6>
              <p class="testimonial__location">場所1</p>
            </address>
          </div>
        </div>

        <div class="slide slide--2">
          <div class="testimonial">
            <h5 class="testimonial__header">
              スライド2
            </h5>
            <blockquote class="testimonial__text">
              ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。
            </blockquote>
            <address class="testimonial__author">
              <img src="http://unsplash.it/50/50/?random" alt="" class="testimonial__photo" />
              <h6 class="testimonial__name">名前2</h6>
              <p class="testimonial__location">場所2</p>
            </address>
          </div>
        </div>

        <div class="slide slide--3">
          <div class="testimonial">
            <h5 class="testimonial__header">
              スライド3
            </h5>
            <blockquote class="testimonial__text">
              ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。ここに文章。
            </blockquote>
            <address class="testimonial__author">
              <img src="http://unsplash.it/50/50/?random" alt="" class="testimonial__photo" />
              <h6 class="testimonial__name">名前3</h6>
              <p class="testimonial__location">場所3</p>
            </address>
          </div>
        </div>
//画像をスライドさせる場合
        <div class="slide"><img src="http://unsplash.it/500/500/?random" alt="Photo 1" /></div>
        <div class="slide"><img src="http://unsplash.it/500/500/?random" alt="Photo 2" /></div>
        <div class="slide"><img src="http://unsplash.it/500/500/?random" alt="Photo 3" /></div>
        <div class="slide"><img src="http://unsplash.it/500/500/?random" alt="Photo 4" /></div>
        <button class="slider__btn slider__btn--left">&larr;</button>
        <button class="slider__btn slider__btn--right">&rarr;</button>
        <div class="dots"></div>
      </div>
CSS

* {
  margin: 0;
  padding: 0;
  box-sizing: inherit;
}

html {
  font-size: 62.5%;
  box-sizing: border-box;
}

body {
  font-family: 'Poppins', sans-serif;
  font-weight: 300;
  color: #444;
  line-height: 1.9;
  background-color: #f3f3f3;
}

/* SLIDER */
.slider {
  max-width: 100rem;
  height: 50rem;
  margin: 0 auto;
  position: relative;

  /* IN THE END */
  overflow: hidden;
}

.slide {
  position: absolute;
  top: 0;
  width: 100%;
  height: 50rem;

  display: flex;
  align-items: center;
  justify-content: center;

  /* THIS creates the animation! */
  transition: transform 1s;
}

.slide > img {
  /* Only for images that have different size than slide */
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.slider__btn {
  position: absolute;
  top: 50%;
  z-index: 10;

  border: none;
  background: rgba(255, 255, 255, 0.7);
  font-family: inherit;
  color: #333;
  border-radius: 50%;
  height: 5.5rem;
  width: 5.5rem;
  font-size: 3.25rem;
  cursor: pointer;
}

.slider__btn--left {
  left: 6%;
  transform: translate(-50%, -50%);
}

.slider__btn--right {
  right: 6%;
  transform: translate(50%, -50%);
}



.dots {
  position: absolute;
  bottom: 5%;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
}

.dots__dot {
  border: none;
  background-color: #b9b9b9;
  opacity: 0.7;
  height: 1rem;
  width: 1rem;
  border-radius: 50%;
  margin-right: 1.75rem;
  cursor: pointer;
  transition: all 0.5s;

  /* Only necessary when overlying images */
  /* box-shadow: 0 0.6rem 1.5rem rgba(0, 0, 0, 0.7); */
}

.dots__dot:last-child {
  margin: 0;
}

.dots__dot--active {
  /* background-color: #fff; */
  background-color: #888;
  opacity: 1;
}


/* TESTIMONIALS */
.testimonial {
  width: 65%;
  position: relative;
}

.testimonial::before {
  content: '\201C';
  position: absolute;
  top: -5.7rem;
  left: -6.8rem;
  line-height: 1;
  font-size: 20rem;
  font-family: inherit;
  color: var(--color-primary);
  z-index: -1;
}

.testimonial__header {
  font-size: 2.25rem;
  font-weight: 500;
  margin-bottom: 1.5rem;
}

.testimonial__text {
  font-size: 1.7rem;
  margin-bottom: 3.5rem;
  color: #666;
}

.testimonial__author {
  margin-left: 3rem;
  font-style: normal;

  display: grid;
  grid-template-columns: 6.5rem 1fr;
  column-gap: 2rem;
}

.testimonial__photo {
  grid-row: 1 / span 2;
  width: 6.5rem;
  border-radius: 50%;
}

.testimonial__name {
  font-size: 1.7rem;
  font-weight: 500;
  align-self: end;
  margin: 0;
}

.testimonial__location {
  font-size: 1.5rem;
}

.section__title--testimonials {
  margin-bottom: 4rem;
}

JavaScript


///////////////////////////////////////
// Slider Component

const sliders = function () {
  //一番上から下まで囲う

//要素を定義
  const slides = document.querySelectorAll('.slide');
  const btnLeft = document.querySelector('.slider__btn--left');
  const btnRight = document.querySelector('.slider__btn--right');
  const dotContainer = document.querySelector('.dots');

  
  //cur = current、現在のスライド
  let curSlide = 0;
  //スライドの限界枚数を設定する
  const maxSlide = slides.length; //slidesのNodeListのlength。Nodeもlengthをとれる
  //lengthは0ベースではないので、4枚なら1-4

// Functions
  //ドット部分を作成
  //slidesの中のslideの数だけドットを作りたい。
  // slidesをforEachにかけて、insertAdjacentHTMLで挿入する
  const createDots = function () {
    //indexだけ必要なので、iとして、第一引数は_で使わないを意味する。
    slides.forEach(function (_, i) {
      dotContainer.insertAdjacentHTML(
        'beforeend',
        ``
      );
    });
  };

  //activeなドットの色を濃くする
  const activateDot = function (slide) {
    // activeをつける前に全部からactiveをとる;
    document
      .querySelectorAll('.dots__dot')
      .forEach(dot => dot.classList.remove('dots__dot--active'));
    document
      .querySelector(`.dots__dot[data-slide="${slide}"]`)
      .classList.add('dots__dot--active');
  };

  //これは全部表示して動きがみえるようにするためのコードスケール小さくして全部の画像をみれるようにして、ボタンを押したときの挙動を確認した。
  // const slider = document.querySelector('.slider');
  // slider.style.transform = 'scale(0.4) translateX(-1200px)';
  // slider.style.overflow = 'visible';
  const goToSlide = function (slide) {
    slides.forEach(
      (s, i) => (s.style.transform = `translateX(${100 * (i - slide)}%)`)
    );
    // curSlide = 1のとき: -100%, 0%, 100%, 200%, i=0でcurSlideは先にcurSlide++で1たしているので (i - curSlide)は(0-1)という数字になります。つまり、100 * (-1)となり0番目の画像は-100%になるということです。そしてその次の画像は、(i - curSlide)が100 * (1-1)なので0, その次は100 * (2-1)なので100%になります。
  };
  // 上の意味はこれ↓
  //slideにtranslateXを設定する
  //s = slide, i = index
  //translateX => 0%, 100%, 200%, 300%
  // slides.forEach((s, i) => (s.style.transform = `translateX(${100 * i}%)`));

  // 次のスライドへ移行する
  const nextSlide = function () {
    // maxSlideに達したら0にする。
    if (curSlide === maxSlide - 1) {
      //maxSlideはlengthなので1-4、0-3の3で止まってほしいので-1する
      curSlide = 0;
    } else {
      curSlide++;
    }
    goToSlide(curSlide);
    activateDot(curSlide);
  };

  const prevSlide = function () {
    if (curSlide === 0) {
      curSlide = maxSlide - 1;
    } else {
      curSlide--;
    }
    goToSlide(curSlide);
    activateDot(curSlide);
  };

  // 最初の状態を設定する
  const init = function () {
    //一度だけ呼び出したい。i=0にすることで、0%, 100%, 200%, 300%になる。↓を参照
    goToSlide(0);
    createDots(); //createDotsはactivateDotの前に実行する必要がある。
    //最初の状態でインデックスの0番にactiveがついてほしい
    activateDot(0);
  };
  init();

  // Event handlers
  // translateXの値を操作する。
  btnRight.addEventListener('click', nextSlide);
  btnLeft.addEventListener('click', prevSlide);

  //矢印キーでも反応するようにする。
  document.addEventListener('keydown', function (e) {
    console.log(e); //右矢印と左矢印を押して名前を確認 ArrowLeft, ArrowRight
    // どちらの書き方でもOK
    //if文
    if (e.key === 'ArrowLeft') prevSlide();
    //short cuirciting
    e.key === 'ArrowRight' && nextSlide();
  });

  dotContainer.addEventListener('click', function (e) {
    if (e.target.classList.contains('dots__dot')) {
      // console.log('DOT');
      //これでもいいし↓の分割代入でもいい
      // const slide = e.target.dataset.slide;
      //分割代入を使う
      const { slide } = e.target.dataset;
      console.log(slide); //0,1,2,3のいずれかが表示される
      goToSlide(slide);
      //ここはcurSlideではなくslide
      activateDot(slide);
    }
  });
}; //sliders()のための閉じカッコ
sliders();

全体をsliders関数にまとめる。



const sliders = function () {
  //一番上から下まで囲う
//ここに中身をかく。
}; //sliders()のための閉じカッコ
sliders();

最初でも最後でもいいのですが、全体を囲って関数にします。そして、呼び出ししておきます。

要素を定義する



//要素を定義
  const slides = document.querySelectorAll('.slide');
  const btnLeft = document.querySelector('.slider__btn--left');
  const btnRight = document.querySelector('.slider__btn--right');
  const dotContainer = document.querySelector('.dots');

  
  //cur = current、現在のスライド
  let curSlide = 0;
  //スライドの限界枚数を設定する
  const maxSlide = slides.length; //slidesのNodeListのlength。Nodeもlengthをとれる
  //lengthは0ベースではないので、4枚なら1-4

使う要素を定義します。
スライドで重要な点は、現在のスライドと最大のスライドを定義することです。
現在のスライドはインデックス番号で操作していくので0がいいでしょう。
最大のスライドは設定しないとどこまでもスライドが移動してしまうので、lengthで設定します。
ただし、lengthは0ベースではなくインデックスと1ずれるので計算のときにはmaxSlide-1として計算します。

画像を小さくしてみれるようにする


  //これは全部表示して動きがみえるようにするためのコードスケール小さくして全部の画像をみれるようにして、ボタンを押したときの挙動を確認した。
  // const slider = document.querySelector('.slider');
  // slider.style.transform = 'scale(0.4) translateX(-1200px)';
  // slider.style.overflow = 'visible';

最終的には不要になりますが、最初のコードを書くときにはすべて見えておいたほうが動きがわかっていいでしょう。

スライドの右矢印と左矢印でスライドする関数をつくる


  const goToSlide = function (slide) {
    slides.forEach(
      (s, i) => (s.style.transform = `translateX(${100 * (i - slide)}%)`)
    );
    // curSlide = 1のとき: -100%, 0%, 100%, 200%, i=0でcurSlideは先にcurSlide++で1たしているので (i - curSlide)は(0-1)という数字になります。つまり、100 * (-1)となり0番目の画像は-100%になるということです。そしてその次の画像は、(i - curSlide)が100 * (1-1)なので0, その次は100 * (2-1)なので100%になります。
  };
  // 上の意味はこれ↓
  //slideにtranslateXを設定する
  //s = slide, i = index
  //translateX => 0%, 100%, 200%, 300%
  // slides.forEach((s, i) => (s.style.transform = `translateX(${100 * i}%)`));

  // 次のスライドへ移行する
  const nextSlide = function () {
    // maxSlideに達したら0にする。
    if (curSlide === maxSlide - 1) {
      //maxSlideはlengthなので1-4、0-3の3で止まってほしいので-1する
      curSlide = 0;
    } else {
      curSlide++;
    }
    goToSlide(curSlide);
    activateDot(curSlide);
  };

  const prevSlide = function () {
    if (curSlide === 0) {
      curSlide = maxSlide - 1;
    } else {
      curSlide--;
    }
    goToSlide(curSlide);
    activateDot(curSlide);
  };

  // Event handlers
  // translateXの値を操作する。
  btnRight.addEventListener('click', nextSlide);
  btnLeft.addEventListener('click', prevSlide);

slideにtranslateXを設定をします。

translateX => 0%, 100%, 200%, 300%として、100%ずつずらすことで、見えるスライドを調整することができます。
s = slide, i = indexとして、

slides.forEach((s, i) => (s.style.transform = `translateX(${100 * i}%)`));

curSlide = 1のとき: -100%, 0%, 100%, 200%, i=0でcurSlideは先にcurSlide++で1たしているので (i – curSlide)は(0-1)という数字になります。
つまり、100 * (-1)となり0番目の画像は-100%になるということです。そしてその次の画像は、(i – curSlide)が100 * (1-1)なので0, その次は100 * (2-1)なので100%になります。

次のスライドへ移行する関数は、maxSlideに達したら0にして、それ以上スライドできないようにします。
その際、maxSlideはlengthなので1-4であれば、インデックス番号でいう0-3の3で止まってほしいので-1にします。

前のスライドへ移行する関数は、上と逆のことをすればOKです。最初のスライドに達したときは、インデックス番号を考慮してmaxSlide-1とすることで、最後のスライドに移動させることができます。

矢印キーでも反応する



  // Event handlers
  //矢印キーでも反応するようにする。
  document.addEventListener('keydown', function (e) {
    console.log(e); //右矢印と左矢印を押して名前を確認 ArrowLeft, ArrowRight
    // どちらの書き方でもOK
    //if文
    if (e.key === 'ArrowLeft') prevSlide();
    //short cuirciting
    e.key === 'ArrowRight' && nextSlide();
  });

キーボードの矢印キーでも反応するように、keydownを使います。
ここはシンプルで、右矢印がおされたら、次へ、左矢印がおされたら、前への関数を実行します。
書き方は普通のif文でもショートサーキティング(&&だとfalsyな値が出るまで実行されず最後のものが実行される)を使ってもどちらでもいいでしょう。

ドットをクリックしたらスライドが動くように設定する



// Functions
  //ドット部分を作成
  //slidesの中のslideの数だけドットを作りたい。
  // slidesをforEachにかけて、insertAdjacentHTMLで挿入する
  const createDots = function () {
    //indexだけ必要なので、iとして、第一引数は_で使わないを意味する。
    slides.forEach(function (_, i) {
      dotContainer.insertAdjacentHTML(
        'beforeend',
        ``
      );
    });
  };

  //activeなドットの色を濃くする
  const activateDot = function (slide) {
    // activeをつける前に全部からactiveをとる;
    document
      .querySelectorAll('.dots__dot')
      .forEach(dot => dot.classList.remove('dots__dot--active'));
    document
      .querySelector(`.dots__dot[data-slide="${slide}"]`)
      .classList.add('dots__dot--active');
  };

  // Event handlers
  dotContainer.addEventListener('click', function (e) {
    if (e.target.classList.contains('dots__dot')) {
      // console.log('DOT');
      //これでもいいし↓の分割代入でもいい
      // const slide = e.target.dataset.slide;
      //分割代入を使う
      const { slide } = e.target.dataset;
      console.log(slide); //0,1,2,3のいずれかが表示される
      goToSlide(slide);
      //ここはcurSlideではなくslide
      activateDot(slide);
    }
  });

次はslidesの中のslideの数だけドットを作ります。
ドットの作り方はシンプルで、slidesをforEachにかけて、insertAdjacentHTMLで挿入します。

現在のスライドとドットの色が紐付いているとわかりやすいので、activeスライドのドットの色を他と違うように変化させます。
そのために、いったんactiveをつける前に全部からactiveをとり、現在のスライドのみにactiveをつけます。

その際、カスタムデータ属性が役に立ちます。
data-slideという属性で値がforEachで変化するように設定しています。