Swiperのデモを作ってみました(サンプルコードあり)

Swiperは多機能だなぁと思う反面、細部をいじっていると結構はまることがあります。
自分のコードが悪いのかSwiperの機能がそうなのか、想像したとおりに動作しないことが多々あります。
そのたびに、他の方のコードを参考にいろいろ試してみました(先達の皆さまに感謝!)。

ということで、今回は仕事でつかえそうなコードを書きました。
よくあるパターンを取り入れていますので、サイト制作でお役立てください。

多機能スライダーSwiperとは

基本的な内容は、下記の記事を参考にどうぞ。
コード例を中心に学習したい方は、Swiper公式サイトのデモページを参照ください。

デモ前のかんたんな説明

スマートフォン時のスタイルは入っていませんので、適当に入れてください。

Swiperの機能としては、パララックス(Parallax)とフェード(Fade)が見映えがよいです。

CSSをいじってたら崩れた、ということがよくあります。
凝った作り込みをすると、思い通りに動作しないことがしょっちゅうです。

スライドの切替時にエフェクト

スライドの切替時にスライドするのではなく、黒い幕を開閉するエフェクトを入れて、切り替えるようにしました。CSSメインのエフェクトです。

デモサイトを開く「DEMO 01

サンプルコード

<!-- Link Swiper's CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@9/swiper-bundle.min.css" />

<style>
  .swiper {
    max-width: 1600px;
  }

  .swiper-wrapper {
    height: auto;
  }

  .swiper-slide img {
    width: 100%;
    height: auto;
  }

  .pic {
    position: relative;
    padding-top: calc(50%);
    box-sizing: border-box;
    overflow: hidden;
    z-index: 2;
  }

  .pic img {
    display: block;
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    filter: grayscale(0.5);
    object-fit: cover;
    z-index: 1;
  }

  /* 黒い幕 */
  .mask {
    display: block;
    position: absolute;
    top: 0;
    bottom: 0;
    right: 0;
    left: 0;
    background: linear-gradient(to right, #000 0%, #333 100%);
    transform: scale(0, 1);
    transform-origin: right top;
    transition: transform .5s ease 0s;
    z-index: 2;
  }

  .is-loaded .mask {
    transform: scale(1, 1);
  }

  .is-opened .mask {
    transform: scale(0, 1);
  }

  .is-closed .mask {
    transform-origin: left top;
    transform: scale(1, 1);
  }

  /* 画像のズームアウト */
  .zoomOut img {
    transform: scale(1.2) translate(0, 0);
    transition: transform 7s ease;
  }

  .zoomOut.swiper-slide-active img {
    transform: scale(1.1) translate(40px, -20px);
  }

  /* キャプション */
  .caption {
    color: #000;
    text-shadow: 2px 2px 5px #eee, -2px -2px 5px #eee;
    font-size: 40px;
    font-weight: bold;
    text-align: right;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translateX(-50%) translateY(-50%);
    z-index: 5;
  }

  /* フェード+ゆっくり移動 処理が重いときは外す*/
  .swiper-slide-active .caption .fadeUp {
    animation-name: fadeUpAnime;
    animation-duration: 1s;
    animation-fill-mode: forwards;
    opacity: 0;
  }

  @keyframes fadeUpAnime {
    from {
      opacity: 0;
      transform: translateX(-100px);
      filter: blur(10px);
    }

    to {
      opacity: 1;
      transform: translateX(0);
      filter: blur(0);
    }
  }

  .caption div:nth-of-type(1) {
    animation-delay: 1s;
  }

  .caption div:nth-of-type(2) {
    color: #700;
    animation-delay: 2s;
    font-size: 0.8em;
  }

  /* ページネーション関連 */
  .swiper-pagination-bullet {
    --swiper-theme-color: rgb(0, 0, 0);
    --swiper-pagination-bullet-width: 4px;
    --swiper-pagination-bullet-height: 4px;
    --swiper-pagination-bullet-border-radius: none;
    transition: .5s;
  }

  .swiper-pagination-bullet-active {
    --swiper-pagination-bullet-width: 20px;
  }
</style>

<!-- Slider main container -->
<div class="swiper">
  <!-- Additional required wrapper -->
  <div class="swiper-wrapper">
    <!-- Slides -->
    <div class="swiper-slide zoomOut">
      <div class="pic">
        <div class="mask"></div>
        <img src="https://naeco.jp/demo/img/ss01.jpg">
        <div class="swiper-lazy-preloader"></div>
      </div>
      <div class="caption">
        <div class="fadeUp">Lorem ipsum dolor sit amet,</div>
        <div class="fadeUp">consectetur adipiscing elit</div>
      </div>
    </div>
    <div class="swiper-slide zoomOut">
      <div class="pic">
        <div class="mask"></div>
        <img src="https://naeco.jp/demo/img/ss02.jpg">
      </div>
      <div class="caption">
        <div class="fadeUp">Ut enim ad minim veniam,</div>
        <div class="fadeUp">quis nostrud exercitation ullamco</div>
      </div>
    </div>
    <div class="swiper-slide zoomOut">
      <div class="pic">
        <div class="mask"></div>
        <img src="https://naeco.jp/demo/img/ss03.jpg">
      </div>
      <div class="caption">
        <div class="fadeUp">Duis aute irure dolor in</div>
        <div class="fadeUp">reprehenderit in voluptate</div>
      </div>
    </div>
    <div class="swiper-slide zoomOut">
      <div class="pic">
        <div class="mask"></div>
        <img src="https://naeco.jp/demo/img/ss04.jpg">
      </div>
      <div class="caption">
        <div class="fadeUp">Excepteur sint occaecat</div>
        <div class="fadeUp">cupidatat non proident,</div>
      </div>
    </div>
  </div>
  <!-- If we need pagination -->
  <div class="swiper-pagination"></div>
</div>

<!-- Swiper JS -->
<script src="https://cdn.jsdelivr.net/npm/swiper@9/swiper-bundle.min.js"></script>

<script>
  const swiper = new Swiper('.swiper', {
    // Optional parameters
    loop: true,
    speed: 500,
    autoplay: {
      delay: 6000,
      disableOnInteraction: false,
    },
    // If we need pagination
    pagination: {
      el: '.swiper-pagination',
      clickable: true,
    },
    on: {
      beforeTransitionStart: function () {
        const elem5 = document.querySelectorAll('.pic');
        elem5.forEach(function (elem) {
          elem.classList.add('is-closed');
        });
      },
      slideChangeTransitionEnd: function () {
        const elem6 = document.querySelectorAll('.pic');
        elem6.forEach(function (elem) {
          elem.classList.remove('is-closed');
        });
      },
    },
  });
</script>

<script>
  window.addEventListener('load', (event) => {
    const elem1 = document.querySelector('.pic');
    elem1.classList.add('is-loaded');
  });

  const elem2 = document.querySelector('.mask');
  elem2.addEventListener('transitionend', () => {
    const elem3 = document.querySelector('.pic');
    if (elem3.classList.contains('is-loaded')) {
      const elem4 = document.querySelectorAll('.pic');
      elem4.forEach(function (elem) {
        elem.classList.add('is-opened');
        elem.classList.remove('is-loaded');
      });
    }
  });
</script>


CSS 20行目…padding-topをつかって、縦横比を指定しています。
CSS 69-72行目…じわじわとズームアウトしながら移動します(よく見かけるアレ)。
CSS 126-129行目…カスタムプロパティ(CSS変数、--swiper-*)はサイト全体に効かせたい場合は、:root{}内に入れます。複数設置することが多いので、全体に一律で効かせることはあまりないかもしれませんが。

HTML 147行目…読込中にまわるスピナー(Swiper標準装備)を設置しました。一枚目の画像の下に書きます。読み込みが終わると勝手に消えます。
HTML 149-151行目…複数行のtransformがあるときは、要素を分けないと動作しません。

パラメーターとイベント

clickable: truepagenationでつかう。
ページネーションタイプがbullets(丸)の場合、ページネーションボタンをクリックすると適切なスライドに遷移します。
disableOnInteraction: falseautoplayでつかう。
ユーザー操作 (スワイプ) の後でも自動再生は無効になりません。
beforeTransitionStartイベント。遷移開始前にイベントが発生します。
slideChangeTransitionEndイベント。他のスライド (次または前) へのアニメーションの後に発生します。

2枚並べて縦に上下スライド

上下にスライドさせるスライダーを横に並べて、ダイナミックな動きをつけました。
中央の文字の切り替わりもスライダー(mySwiper3)で実装しています。

デモサイトを開く「DEMO 02

サンプルコード

<!-- Link Swiper's CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@9/swiper-bundle.min.css" />

<style>
body {
  margin: 0; /* 画像にわずかなズレがあるときはここ */
}

.wrapper {
  display: flex;
}

.swiper-pagination-bullet {
  --swiper-pagination-bullet-width: 4px;
  --swiper-pagination-bullet-height: 4px;
  --swiper-pagination-bullet-border-radius: none;
  transition: 1s;
}

.swiper-pagination-vertical.swiper-pagination-bullets .swiper-pagination-bullet,
.swiper-vertical>.swiper-pagination-bullets .swiper-pagination-bullet {
  --swiper-theme-color: #700;
  margin-left: auto;
}

.swiper-pagination-bullet-active {
  --swiper-pagination-bullet-width: 20px;
}

.mySwiper,
.mySwiper2 {
  width: 50%;
  height: 100vh;
  overflow: hidden;
}

.mySwiper3 {
  position: absolute;
  top: 50%;
  left: 50%;
  width: auto;
  height: 100px;
  transform: translate(-50%, -100%);
  font-size: 40px;
  color: #000;
  text-align: center;
  text-shadow: 1px 1px 1px #000, -1px 1px 1px #000, 1px -1px 1px #000, -1px -1px 1px #000;
}

.swiper-slide {
  height: 100vh;
  position: relative;
}

.swiper-slide div {
  width: 100%;
  height: 100%;
  overflow: hidden;
}

.swiper-slide img {
  filter: grayscale(0.9);
  object-fit: cover;
  height: 100%;
  z-index: 1;
}

.mySwiper .swiper-slide img {
  object-position: 0 top;
}

.mySwiper2 .swiper-slide img {
  object-position: -50vw top;
}

.mySwiper .swiper-slide-active img,
.mySwiper2 .swiper-slide-active img {
  filter: grayscale(0.5);
}

.mySwiper3 .swiper-slide-active .fade {
  animation-name: fadeUpAnime;
  animation-duration: 1s;
  animation-fill-mode: forwards;
  opacity: 0;
}

@keyframes fadeUpAnime {
  from {
    opacity: 0;
    filter: blur(50px);
  }

  to {
    opacity: 1;
    filter: blur(0);
  }
}
</style>

<div class="wrapper">
  <!-- Swiper (左スライダー)-->
  <div class="swiper mySwiper">
    <div class="swiper-wrapper">
      <div class="swiper-slide">
        <div>
          <img src="https://naeco.jp/demo/img/ss01.jpg">
        </div>
      </div>
      <div class="swiper-slide">
        <div>
          <img src="https://naeco.jp/demo/img/ss02.jpg">
        </div>
      </div>
      <div class="swiper-slide">
        <div>
          <img src="https://naeco.jp/demo/img/ss03.jpg">
        </div>
      </div>
      <div class="swiper-slide">
        <div>
          <img src="https://naeco.jp/demo/img/ss04.jpg">
        </div>
      </div>
    </div>
  </div>
  <!-- Swiper (右スライダー)-->
  <div class="swiper mySwiper2">
    <div class="swiper-wrapper">
      <div class="swiper-slide">
        <div>
          <img src="https://naeco.jp/demo/img/ss01.jpg">
        </div>
      </div>
      <div class="swiper-slide">
        <div>
          <img src="https://naeco.jp/demo/img/ss04.jpg">
        </div>
      </div>
      <div class="swiper-slide">
        <div>
          <img src="https://naeco.jp/demo/img/ss03.jpg">
        </div>
      </div>
      <div class="swiper-slide">
        <div>
          <img src="https://naeco.jp/demo/img/ss02.jpg">
        </div>
      </div>
    </div>
    <!-- If we need pagination -->
    <div class="swiper-pagination"></div>
  </div>
  <!-- Swiper (文字)-->
  <div class="swiper mySwiper3">
    <div class="swiper-wrapper">
      <div class="swiper-slide">
        <div class="fade">Lorem ipsum dolor sit amet</div>
      </div>
      <div class="swiper-slide">
        <div class="fade">Ut enim ad minim veniam</div>
      </div>
      <div class="swiper-slide">
        <div class="fade">Duis aute irure dolor in reprehenderit</div>
      </div>
      <div class="swiper-slide">
        <div class="fade">Excepteur sint occaecat cupidatat non proident</div>
      </div>
    </div>
  </div>
</div>

<!-- Swiper JS -->
<script src="https://cdn.jsdelivr.net/npm/swiper@9/swiper-bundle.min.js"></script>

<!-- Initialize Swiper -->
<script>
  const swiper = new Swiper(".mySwiper", {
    allowTouchMove: false,
    direction: "vertical",
    loop: true,
    speed: 1000,
    simulateTouch: false,
    autoplay: {
      delay: 5000,
      disableOnInteraction: false,
      reverseDirection: true, //スライド逆回転
      pauseOnMouseEnter: false,
    },
    freeMode: {
      enabled: true,
      momentum: false,
    },
  });

  const swiper2 = new Swiper(".mySwiper2", {
    allowTouchMove: false,
    direction: "vertical",
    loop: true,
    speed: 1000,
    simulateTouch: false,
    autoplay: {
      delay: 5000,
      disableOnInteraction: false,
      pauseOnMouseEnter: false,
    },
    // If we need pagination
    pagination: {
      el: '.swiper-pagination',
    },
    freeMode: {
      enabled: true,
      momentum: false,
    },
  });

  const swiper3 = new Swiper(".mySwiper3", {
    allowTouchMove: false,
    direction: "vertical",
    loop: true,
    speed: 1000,
    simulateTouch: false,
    autoplay: {
      delay: 5000,
      disableOnInteraction: false,
      pauseOnMouseEnter: false,
    },
    freeMode: {
      enabled: true,
      momentum: false,
    },
  });

  //スライドごとのズレをリセットする
  swiper.on('slideChangeTransitionEnd', () => {
    swiper2.on('slideChangeTransitionEnd', () => {
      swiper.autoplay.stop();
      swiper2.autoplay.stop();
      swiper3.autoplay.stop();
      swiper.autoplay.start();
      swiper2.autoplay.start();
      swiper3.autoplay.start();
    });
  });
  swiper2.on('slideChangeTransitionEnd', () => {
    swiper.on('slideChangeTransitionEnd', () => {
      swiper.autoplay.stop();
      swiper2.autoplay.stop();
      swiper3.autoplay.stop();
      swiper.autoplay.start();
      swiper2.autoplay.start();
      swiper3.autoplay.start();
    });
  });
  swiper.on('resize', () => {
    swiper.autoplay.stop();
    swiper2.autoplay.stop();
    swiper3.autoplay.stop();
    swiper.autoplay.start();
    swiper2.autoplay.start();
    swiper3.autoplay.start();
  });
</script>


パソコン閲覧時にブラウザを最大化・縮小すると、画像のスライドするタイミングが左右で揃わなくなります(逆回転の影響があるらしい)。
余談ですが、swiper大外にdir="rtl"をつけるとスライドの方向が逆(左←右)になります。

CSS6行目…ブラウザのデフォルトのスタイルでmarginが入っていますので、0にします。ズレの原因になります。
CSS 74行目…右スライドの画像表示をobject-positionでずらしています。

JS 187行目…controllerという各スライダーを連動させるモジュールがありますが、逆回転(reverseDirection: true)を入れていると正常動作しませんでした。
JS 190-193行目…freeModeを入れるとタイミングをリセットしてくれるようです。
JS 237-242行目…swiper.autoplay.stop()、swiper.autoplay.start()で待ち時間をリセットできます。毎回スライダーごとのズレを揃えています。ここのコードは工夫の余地があるかもしれません。

パラメーターとイベント

allowTouchMove: falseズレる原因になるので、タッチ操作を許可しません。
direction: "vertical"垂直方向にまわるスライダー。
simulateTouch: falseズレる原因になるので、マウスイベント、タッチイベントを許可しません。
reverseDirection: trueautoplayでにつかう。スライドの逆回転。
1→4→3→2とまわります。dir="rtl"とはまわり順が違います。
resizeイベント。ウィンドウのサイズ変更時にイベントが発生します。

商品を見せるのに使えそうな2枚横並び

画像2枚を横に並べて、エフェクトにフェードとパララックスを使っています。
商品を見せるのによさそうな見せ方です。

デモサイトを開く「DEMO 03

サンプルコード

<!-- Link Swiper's CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@9/swiper-bundle.min.css" />

<style>
  .wrapper {
    display: flex;
    justify-content: center;
    align-items: center;
    width: 100%;
    height: 100%;
  }

  .swiper {
    --swiper-theme-color: #777;
    --swiper-pagination-bullet-width: 10px;
    --swiper-pagination-bullet-height: 10px;
    --swiper-pagination-bullet-border-radius: none;
    max-width: 1200px;
    height: 600px;
    background: #f9f9f9;
  }

  .swiper-button-next,
  .swiper-rtl .swiper-button-prev {
    --swiper-navigation-sides-offset: calc(50% + 10px);
  }

  .swiper-slide {
    display: flex;
    overflow: hidden;
  }

  .swiper-slide .image img {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  .swiper-slide .text,
  .swiper-slide .image {
    width: 50%;
    overflow: hidden;
    text-align: center;
  }

  .swiper-slide .text {
    color: #555;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-wrap: wrap;
    flex-direction: column;
    text-align: center;
    overflow: hidden;
  }

  @media (min-width: 1200px) {
    .swiper-slide .text {
      flex-direction: row;
    }
  }

  .swiper-slide .text img {
    width: 200px;
    background: #eee;
    border: 1px solid #999;
    margin: 10px;
    padding: 5px;
  }

  .pagenation-wrapper {
    width: 50%;
    position: absolute;
    bottom: 0;
    left: 0;
  }

  .title {
    color: #444;
    font-size: 1.2em;
    letter-spacing: 1px;
    margin: 10px;
  }

  .title:before {
    content: '';
    position: absolute;
    bottom: -5px;
    left: 50%;
    display: inline-block;
    width: 60px;
    height: 1px;
    transform: translateX(-50%);
    background-color: #bbb;
    border-radius: 2px;
  }

  .price {
    color: #700;
  }

  /* 斜めの背景 */
  .slant-bg {
    position: relative;
    width: 100%;
    margin: 0;
    overflow: hidden;
  }

  .slant-bg p {
    width: 65%;
    margin: 0 auto;
  }

  .slant-bg::before {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    transform: skewY(-7deg) translateY(-90px);
    transition: 10s;
    z-index: -1;
  }

  /* 背景色 */
  .swiper-slide:nth-of-type(1) .slant-bg::before {
    background: #ffcfcf;
  }

  .swiper-slide:nth-of-type(2) .slant-bg::before {
    background: #B1D7F9;
  }

  .swiper-slide:nth-of-type(3) .slant-bg::before {
    background-color: #F2E7AC;
  }

  .swiper-slide:nth-of-type(4) .slant-bg::before {
    background-color: #D1E28A;
  }
</style>

<div class="wrapper">
  <div class="swiper">
    <div class="swiper-wrapper">
      <div class="swiper-slide">
        <div class="text slant-bg">
          <img src="https://naeco.jp/demo/img/ss01th.jpg" data-swiper-parallax-x="-50" >
          <h2 class="title" data-swiper-parallax-y="-20">Wooden Table</h2>
          <div class="price" data-swiper-parallax-y="-40" data-swiper-parallax-duration="1500">¥9,800</div>
        </div>
        <div class="image">
          <img src="https://naeco.jp/demo/img/ss01.jpg" data-swiper-parallax-x="-50">
        </div>
      </div>
      <div class="swiper-slide">
        <div class="text slant-bg">
          <img src="https://naeco.jp/demo/img/ss02th.jpg" data-swiper-parallax-x="-50">
          <h2 class="title" data-swiper-parallax-y="-20">Blue Sofa</h2>
          <div class="price" data-swiper-parallax-y="-40" data-swiper-parallax-duration="1500">¥18,000</div>
        </div>
        <div class="image">
          <img src="https://naeco.jp/demo/img/ss02.jpg" data-swiper-parallax-x="-50">
        </div>
      </div>
      <div class="swiper-slide">
        <div class="text slant-bg">
          <img src="https://naeco.jp/demo/img/ss03th.jpg" data-swiper-parallax-x="-50">
          <h2 class="title" data-swiper-parallax-y="-20">Orange Chair</h2>
          <div class="price" data-swiper-parallax-y="-40" data-swiper-parallax-duration="1500">¥13,000</div>
        </div>
        <div class="image">
          <img src="https://naeco.jp/demo/img/ss03.jpg" data-swiper-parallax-x="-50">
        </div>
      </div>
      <div class="swiper-slide">
        <div class="text slant-bg">
          <img src="https://naeco.jp/demo/img/ss04th.jpg" data-swiper-parallax-x="-50">
          <h2 class="title" data-swiper-parallax-y="-20">Gray Sofa</h2>
          <div class="price" data-swiper-parallax-y="-40" data-swiper-parallax-duration="1500">¥25,000</div>
        </div>
        <div class="image">
          <img src="https://naeco.jp/demo/img/ss04.jpg" data-swiper-parallax-x="-50">
        </div>
      </div>
    </div>
    <!-- If we need pagination -->
    <div class="pagenation-wrapper">
      <div class="swiper-pagination"></div>
    </div>
    <!-- If we need navigation buttons -->
    <div class="swiper-button-prev"></div>
    <div class="swiper-button-next"></div>
  </div>
</div>

<!-- Swiper JS -->
<script src="https://cdn.jsdelivr.net/npm/swiper@9/swiper-bundle.min.js"></script>

<!-- Initialize Swiper -->
<script>
  const mySwiper = new Swiper('.swiper', {
    effect: 'fade',
    fadeEffect: {
      crossFade: true
    },
    loop: true,
    speed: 1500,
    parallax: true,
    autoplay: {
      delay: 1000,
    },
    // If we need pagination
    pagination: {
      el: '.swiper-pagination',
    },
    // Navigation arrows
    navigation: {
      nextEl: '.swiper-button-next',
      prevEl: '.swiper-button-prev',
    },
  });
</script>


CSS 25行目…右ナビゲーションボタンの位置を変更しています。
CSS 150行目…左文字は、横幅に応じて縦並びにしています。

JS 206行目…fadeを指定しておかないと、いい感じになりません。

パラメーターとイベント

effect: 'fade'フェード効果。遷移時にじんわり現れて消えます。

2枚横並び+プログレスバー

グリグリ動く、派手めなパララックスが印象的なスライダーです。
たまーに付けたくなるプログレスバーも実装しました。

デモサイトを開く「DEMO 04

サンプルコード

<!-- Link Swiper's CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@9/swiper-bundle.min.css" />

<style>
  h1 {
    max-width: 1600px;
    margin: 0 auto;
  }

  .swiper {
    max-width: 1600px;
  }

  .swiper-wrapper {
    height: auto;
  }

  .swiper-slide {
    display: flex;
    justify-content: center;
    align-items: center;
    width: 100%;
    padding-top: calc(50%);
    box-sizing: border-box;
    overflow: hidden;
  }

  .swiper-slide .image {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    overflow: hidden;
  }

  .swiper-slide .image img {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    overflow: hidden;
    object-fit: cover;
    filter: grayscale(0.9);
  }

  .swiper-slide .image:nth-of-type(2) {
    left: 50%;
  }

  .swiper-slide .image:nth-of-type(2) img {
    left: -50%; /* 元画像のどのあたりを表示するか */
  }

  .caption {
    color: #fff;
    background-color: rgba(112, 0, 0, 0.8);
    max-width: 600px;
    overflow: hidden;
    margin-top: calc(-50%);
    padding: 1.5em;
    box-sizing: border-box;
  }

  .title {
    font-size: 40px;
    font-weight: bold;
    margin: 0;
  }

  .text {
    line-height: 1.6;
    margin: 30px 0 15px;
  }

  /* プログレスバー */
  .autoplay-progress {
    --swiper-theme-color: #ff9;
    position: absolute;
    bottom: 0;
    left: 0;
    z-index: 10;
    width: 100%;
    height: 3px;
    color: var(--swiper-theme-color);
  }

  .autoplay-progress svg {
    --progress: 50;
    position: absolute;
    top: 0;
    left: 0;
    z-index: 10;
    width: 100%;
    height: auto;
    stroke-width: 3px;
    stroke: var(--swiper-theme-color);
    fill: none;
    stroke-dashoffset: calc(100px * (1 - var(--progress))); /* オフセット=実線部分は100%で0 */
    stroke-dasharray: 100px; /* 実線と間隔- - */
  }

  .autoplay-progress line {
    width: 100%;
  }
</style>

<h1 id="#demo04">DEMO 04</h1>
<div class="swiper">
  <div class="swiper-wrapper">
    <div class="swiper-slide">
      <div class="image">
        <img src="https://naeco.jp/demo/img/ss01.jpg" data-swiper-parallax-x="50%" data-swiper-parallax-scale="1.1">
      </div>
      <div class="image">
        <img src="https://naeco.jp/demo/img/ss02.jpg" data-swiper-parallax-x="80%" data-swiper-parallax-scale="1.1">
      </div>
      <div class="caption" data-swiper-parallax-x="80%">
        <div class="title" data-swiper-parallax-x="-50%" data-swiper-parallax-duration="800">Lorem ipsum</div>
        <div class="text" data-swiper-parallax-x="-50%" data-swiper-parallax-duration="1000">Lorem ipsum dolor sit amet, consectetur
          adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </div>
      </div>
    </div>

    <div class="swiper-slide">
      <div class="image">
        <img src="https://naeco.jp/demo/img/ss03.jpg" data-swiper-parallax-x="50%" data-swiper-parallax-scale="1.1">
      </div>
      <div class="image">
        <img src="https://naeco.jp/demo/img/ss04.jpg" data-swiper-parallax-x="80%" data-swiper-parallax-scale="1.1">
      </div>
      <div class="caption" data-swiper-parallax-x="80%">
        <div class="title" data-swiper-parallax-x="-50%" data-swiper-parallax-duration="800">Ut enim</div>
        <div class="text" data-swiper-parallax-x="-50%" data-swiper-parallax-duration="1000">Ut enim ad minim veniam, quis nostrud
          exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. </div>
      </div>
    </div>

  </div>
  <div class="autoplay-progress">
    <svg viewBox="0 0 100 1">
      <line x1="0" y1="0" x2="100" y2="1" />
    </svg>
  </div>
</div>

<!-- Swiper JS -->
<script src="https://cdn.jsdelivr.net/npm/swiper@9/swiper-bundle.min.js"></script>

<script>
  const progressCircle = document.querySelector(".autoplay-progress svg");
  const swiper = new Swiper('.swiper', {
    loop: true,
    speed: 1000,
    autoplay: {
      delay: 3000,
      disableOnInteraction: false,
    },
    parallax: true,
    on: {
      autoplayTimeLeft(s, time, progress) {
        progressCircle.style.setProperty("--progress", 1 - progress);
      },
    },
  });
</script>


CSS 23行目…スライドのpadding-topで縦横比を固定しています。
CSS 28-54行目…2枚の画像横並びは、potision: absolute; で実装しています。
CSS 49行目…右に表示する画像を画面の右方向へずらします。
CSS 77-106行目…プログレスバー。
CSS 100行目…公式のデモに円形の進捗メーターがありますが、コードに不具合があって、SafariやFirefoxでは動作しません。正しくは「px」が必要です。

JS 162-164行目…自動再生中は常時イベントが発生し、残り時間(progress)を取得して、カスタムプロパティ(--progress)に数値をセットします。これをCSS 100行目で計算し、プログレスバーを描画しています。

パラメーターとイベント

autoplayTimeLeftイベント。次のスライドに移行するまでの残り時間 (ミリ秒) と、割合(0~1)が得られます。

おわりに

目をひくメインヴィジュアルがトップページにあったほうが、昨今のサイトっぽいですよね。
Swiperを使って作り込むという作業は結構大変で、今後も適宜情報収集して仕事用に作りためていきたいと思います。

執筆者

えいじ@naeco.jp この記事を書いた人

メーカー系情報システム部門出身の個人事業主。
自作するのが好きですぐに試したくなる、凝り性なWebエンジニア。
カラーミーショップ、モールなどのECについて記事にしています。

ご相談・お問い合わせはこちら