🚪

インジケータ(プログレスバー?)付きのスライドショーをパッと作る

2021/08/31に公開

はじめに

スライドショーってJSのチュートリアルとしてめっちゃやった記憶...
今回は1スライドが終わるまでインジケータを表示しなくてはならなかったので、ちょっと工夫しました。
使用ライブラリはSwiperです。

追記

マウス、フリック移動にも対応しました。

完成

こんな感じです。
JSは大したことやってないです。

const indicators = document.querySelectorAll('.indicator');

const _addActive = (index) => {
  indicators[index].classList.remove('active')
  indicators[index].classList.remove('completed')
  void indicators[index].offsetWidth
  indicators[index].classList.add('active')
}

const handleInitSlide = (index) => {
  indicators[index].classList.add('active');
}

const handleNextSlideChange = (index) => {
  indicators[index].classList.add('active');
  _addActive(index)
  indicators[index-1].classList.add('completed')
}

const handlePrevSlideChange = (index) => {
  [...indicators].map((el, idx) => {
    if(idx > index) el.classList.remove('active');
  })
  _addActive(index)
}

new Swiper('.swiper', {
  autoplay: {
    delay: 2000,
    stopOnLastSlide: true,
    disableOnInteraction: false,
  },
  loop: false,
  //events
  on: {
    init: (s) => {
      indicators[s.activeIndex].classList.add('active')
    },
    slideChange: (s) => {
      handleInitSlide(s.activeIndex)
    },
    slideNextTransitionStart: (s) => {
      handleNextSlideChange(s.activeIndex)
    },
    slidePrevTransitionStart: (s) => {
      handlePrevSlideChange(s.activeIndex)
    }
  },
});

ご覧の通り、初期化したタイミングとスライドが変わったタイミングで、順番にインジケータにクラスを付与しているだけです。
「disableOnInteraction = false」にすると、タッチイベントなどがあっても自動再生が止まらないようにできます。
Swiperはドキュメントがしっかりしているのでとても良いです。イベント系はこちらみてください。
https://swiperjs.com/swiper-api#events

で、その付与されたクラスではアニメーションを発火させています。

&.active {
	&:after {
	  animation: expandIndicatorAnim #{$slideSpeed}s forwards linear;
	}

        &.completed {
          &:after {
            animation: none;
            width: 100%;
          }
        }
}

擬似要素を重ねて、擬似要素だけwidthのアニメーションをさせています。
completedはアニメーション途中に次のスライドに行ったときのためのクラスです。
animationをやめて、アニメーション終了時の「width: 100%」の状態にします。

アニメーションはこれだけ..

@keyframes expandIndicatorAnim {
  from {
    width: 0;
  }

  to {
    width: 100%;
  }
}

forwardsにすれば、それっぽくなりますよね。あとSwiperのdelayとアニメーションの時間はしっかり合わせましょう。

ポイント

スライドを行ったり来たりする際の制御のポイントとしては、「アニメーションの初期化」でした。

const _addActive = (index) => {
  indicators[index].classList.remove('active')
  void indicators[index].offsetWidth
  indicators[index].classList.add('active')
}

アニメーションの初期化は、今回はCSSのクラスを付与し直すことで実現しています。
void indicators[index].offsetWidthがミソで、これがないとうまく初期化されません。
こちらの記事を参考にしたのですが、DOMのリフローを発火させることが大事らしいです。
https://css-tricks.com/restart-css-animation/
記事でも言及されてますが、setTimeoutだとちょっと強引だし、正確ではないですよね。いろんなところで使えそうな気がします、この考え方。

まとめ

ライブラリを併用して不足要件だけカスタマイズする方が楽ですよね。
ただ、今回のSwiperはバージョン7と6で書き方違ったり、要件的にJSの文字制限があったりと悩ましいポイントもありました...

Discussion