【jQuery】クロスフェードスライドショーの作り方を解説

【jQuery】クロスフェードスライドショーの作り方を解説

この記事を書いた人

だいち

PENGIN BLOGメディア編集長。Web業界とは異業種の仕事をしながら、独学でWeb制作の世界に。副業でHP制作やコーディング代行、個人ブログの運営などに取り組み、現在はPENGINにてWebライティングやディレクションをしつつ、メディア運営全般を担当しています。(個人運営ブログ:https://daib-log.com/ )

今回はフェードイン(クロスフェード)するスライドイメージの作成方法をまとめました。

ヒーローイメージにこの技法を取り入れているWebサイトを多く見かけますが、商品紹介のスライドショーとしてクロスフェードで画像を見せるようなこともあり、汎用性の高いデザインパターンかと思います。

全体的なコードはカルーセルの記事をベースにしているので、詳細解説を確認されたい方は先にこちらからご覧ください。

フェードインと合わせて、スライド送りボタンページネーション機能も付けた形になっています。

こちらもプラグインを使わず一から組み立ててみますので、勉強中の方も良かった参考にしてみてください。

クロスフェードとは

フェードインとフェードアウトを交互に繰り返す映像編集技術用語のひとつです。

Webサイトではスライドなどの要素がふわっと表示と非表示を繰り返すスライドショーの表現を指します。

今回はかなりシンプルに下記デモサイトを完成形として作成していきます。

5枚の写真をフェードで切り替わるようにしています。

スライドの配置

<h1 class="demo-title">カルーセル デモサイト</h1>
<ul class="fade-area">
    <!-- 意味的に画像はimgタグで配置してるが、CSSで非表示にする。 -->
    <li class="fade-list"><img class="fade-img" src="img/spring.jpg" alt="春のイメージ"></li>
    <li class="fade-list"><img class="fade-img" src="img/rainyseason.jpg" alt="梅雨のイメージ"></li>
    <li class="fade-list"><img class="fade-img" src="img/summer.jpg" alt="夏のイメージ"></li>
    <li class="fade-list"><img class="fade-img" src="img/autumn.jpg" alt="秋のイメージ"></li>
    <li class="fade-list"><img class="fade-img" src="img/winter.jpg" alt="冬のイメージ"></li>
  </ul>

先にHTML全体を載せておきます。肝心な部分は2〜9行目のスライド配置エリアです。

クロスフェードは.fade-areaのスライドをpositionで重ね合わせて、jQueryのfadeOutとfadeInで入れ替えながら表示する仕組みです。

スライドを重ね合わせる

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  list-style: none;
}
.demo-title {
  width: 100%;
  padding: 1em;
  text-align: right;
  position: absolute;
  z-index: 2;
  font-size: 16px;
  color: #fefefe;
}
.fade-area {
  width: 100%;
  height: 600px;
  position: relative;
}
/* object-fitを使用せず画像トリミングさせるためにbackgroundで指定 */
/* fade-areaに対してposition: absoluteで画像を重ねる */
.fade-list {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}
@media screen and (max-width: 600px) {
  .fade-area {
    height: 300px;
  }
}
.fade-list:nth-child(1) {
  background-image: url(./img/spring.jpg);
}
.fade-list:nth-child(2) {
  background-image: url(./img/rainyseason.jpg);
}
.fade-list:nth-child(3) {
  background-image: url(./img/summer.jpg);
}
.fade-list:nth-child(4) {
  background-image: url(./img/autumn.jpg);
}
.fade-list:nth-child(5) {
  background-image: url(./img/winter.jpg);
}
/* imgタグで画像指定したかったためclipで非表示指定(スクリーンリーダー対策) */
.fade-img {
  width: 1px;
  height: 1px;
  clip: rect(1px, 1px, 1px, 1px);
  -webkit-clip-path: inset(50%);
  clip-path: inset(50%);
  margin: -1px;
  padding: 0;
  overflow: hidden;
  position: absolute;
}

スライド表示枠をfade-areaクラスとし、スライドをfade-listクラスとしています。

画像が縦に並んだ状態

そして.fade-areaにposition: relativeを設定し、.fade-listにposition: absoluteで全ての画像を重ねています。

すると縦並びの画像が一枚に重なり、順番的に最後のスライドが表示されているはずです。

positionで重ねた状態
positionで重ねた状態

また、ここではスライド画像を良い感じでトリミングするために背景画像で表示させて、HTML画像はclipで非表示にしているんですが、ここらへんの話も少し詳しくこちらの記事で書いてます。

スライド送りボタンとページネーションを追加

配置する位置を決める

<div class="slide-summary">
    <div class="arow-wrap">
      <div class="arrow-left">
        <button class="arrow-btn js-btn-back" type="button"></button>
      </div>
      <div class="arrow-right">
        <button class="arrow-btn js-btn-next" type="button"></button>
      </div>
    </div>
    <div class="pagination">
      <span class="pagination-circle target"></span>
      <span class="pagination-circle"></span>
      <span class="pagination-circle"></span>
      <span class="pagination-circle"></span>
      <span class="pagination-circle"></span>
    </div>
  </div>

slide-summaryの中に、スライド送りボタンを格納するクラスarow-wrapと、ページネーション用のクラス.paginationを入れています。

先にslide-summaryの位置を決めますが、今回はスライド真下ど真ん中にするのでmargin: 0 autoを指定します。

/*********** スライド補助エリア ***********/
.slide-summary {
  max-width: 600px;
  margin: 0 auto;
  padding: 16px;
  display: flex;
  justify-content: space-around;
  align-items: center;
  background-color: royalblue; /* 位置確認用 */
}

写真のように配置されます。

slide-summary配置

royalblueで色がついた領域にボタンとページネーションが配置されます。

それぞれ横並びに配置するので、display: flexとjustify-content: space-aroundでパーツの位置を指定しています。

ボタン作成

ボタンのデザインを作っていきます。

/*********** スライド送りボタン ***********/
/* 共有パーツ */
.arow-wrap {
  width: 140px;
  display: flex;
  justify-content: space-between;
}
.arrow-btn {
  width: 48px;
  height: 48px;
  background-color: rgb(113, 135, 245);
  border-radius: 5%;
  transition: .2s;
}
.arrow-btn:focus {
  box-shadow: 0px 1px 10px -2px rgba(0, 0, 0, 0.8);
}
.arrow-btn:hover {
  background-color: rgb(51, 79, 216);
  box-shadow: 0px 1px 10px -2px rgba(0, 0, 0, 0.8);
}
/* 左 */
.arrow-left {
  position: relative
}
.arrow-left:before {
  content: "";
  width: 10px;
  height: 10px;
  border-top: 2px solid #fefefe;
  border-left: 2px solid #fefefe;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-30%, -50%)rotate(-45deg);
}
/* 右 */
.arrow-right {
  position: relative
}
.arrow-right:before {
  content: "";
  width: 10px;
  height: 10px;
  border-top: 2px solid #fefefe;
  border-left: 2px solid #fefefe;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-70%, -50%)rotate(135deg);
}

ここも前回の記事とほぼ同じ要領なのでスイマセンが詳細は割愛させてもらいます。

簡単に言ったらarrow-left、arrow-rightに幅と高さを持たせて、それぞれ擬似要素で中身の三角矢印を作っているだけです。

先ほど親要素のslide-summaryにdisplay: flexを仕掛けてるので横並びになったかと思います。

スライド送りボタン完成

ページネーション作成

/*********** ページネーション ***********/
.pagination {
  width: 150px;
  display: flex;
  justify-content: space-around;
}
.pagination-circle {
  width: 20px;
  height: 20px;
  border-radius: 5%;
  background-color: rgba(83, 97, 223, 0.3);
  position: relative;
}
/* jsでtargetクラスがついたら背景色を変える */
.pagination-circle.target::before {
  content: "";
  width: 10px;
  height: 10px;
  display: block;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background-color: rgba(83, 97, 223, 0.8);
}

ここも前回の記事とほぼ同じ要領なので(以下略)、、、。

paginationをflexで横並びにして、中身のpagination-circleに幅と高さを設定しています。

あとはjQueryでtargetというクラスを付与されたpagination-circleクラスだけ、擬似要素で一回り小さいサイズの四角が上下中央位置に表示される、という感じです。

ここまででデザインは完成しましたので次からjQueryを書いていきます。

ページネーション作成

変数定義・スライド表示状態を整理

関数に仕様する変数を定義

まずは現在表示されるスライドが何枚目か?という変数から定義します。基本的にスライドをインデックス数から取得していきます。

////////// スライドの現在値と最終スライド番号を変数化 //////////
  let slideCurrent = 0; // スライド現在値(1枚目のスライド番号としての意味も含む)
  let lastCurrent = $('.fade-list').length - 1; // スライドの合計数=最後のスライド番号

インデックスは0から始まるので1枚目(slideCurrent)は0としています。また、関数でこのslideCurrentの数値は変動するので「現在表示されているスライド番号」として利用されます。

lastCurrentはスライド合計枚数=最終スライド番号として集計しています。

ここも詳細は前回記事に書いてます。

スライドの表示順を整理

//////// 初期スライドだけ表示 //////////
  $('.fade-list').css('display', 'none'); // 一旦すべてのスライドを非表示
  $('.fade-list').eq(slideCurrent).css('display', 'block'); // 最初のスライドを表示

先ほどposition: absoluteで重ね合わせたことで最後のスライドが一番上に表示されていたので、これを元に戻します。

コメントアウトの通りですが、CSSメソッドで一旦すべて非表示にして、eq(slideCurrent) = 現在のスライドを表示させています。

ページ遷移方法を関数化

「changeslide」という名前で関数を定義します。

////////// スライドの切り替わりを「changeslide」として定義(ボタンで取得したスライド番号で変化させる) //////////
  function changeslide() {
    $('.fade-list').fadeOut(1000);
    $('.fade-list').eq(slideCurrent).fadeIn(1500);
  //////// 現在のページに合わせてページネーションを変化させる
    var pagiNation = slideCurrent + 1;
    $('.pagination-circle').removeClass('target');
    $(".pagination-circle:nth-of-type(" + pagiNation + ")").addClass('target');
  };

フェードの動きを定義

$('.fade-list').fadeOut(1000); // 現在のスライドをフェードアウトさせる
$('.fade-list').eq(slideCurrent).fadeIn(1500); // 次のスライドを表示させる

1秒でフェードアウトし、表示させるスライド(slideCurrent)を1.5秒でフェードインさせる、という内容です。

ページネーションの動きを定義

 var pagiNation = slideCurrent + 1; // nth-of-typeで1枚目もカウントとるためスライド数を修正して変数代入
  $('.pagination-circle').removeClass('target'); // targetクラスを削除
  $(".pagination-circle:nth-of-type(" + pagiNation + ")").addClass('target'); // 現在のボタンにtargetクラスを追加

次にページネーションのカウント用にpagiNationという変数を定義しています。

そしてpagination-circleはnth-of-typeでスライド枚数を把握させるのですが、nth-of-typeは1から始まるので、0から始まるインデックス番号を入れてるslideCurrentに+1しています。

pagination-circle:nth-of-type(〜)としてスライド枚数に合わせて、○番目のページネーションにtargetクラスを付与するという式です。

このままだと5枚いったところですべてにtargetクラスがついてしまうので、removeClassで削除と付与を毎回行うという形にしています。

関数作成と実行

自動タイマーやボタンクリックの動作に合わせて関数を呼び出しすますが、この動きはスライドショーの基本の仕組みになります。

ここからの記述は前回のカルーセルスライダーと完全に同じ内容になるため詳細は割愛させてもらいます。詳細確認が必要な方は下記記事を読んでもらえると助かります…。

//////// 自動スライド切り替えのタイマーを設定 //////////
  var Timer;
  // 一定時間毎に処理実行する「startTimer」関数を定義
  function startTimer() {
    Timer = setInterval(function () { // setInterval・・・指定した時間ごとに関数を実行
      if (slideCurrent === lastCurrent) { // 現在のスライドが最終スライドの場合
        slideCurrent = 0;
        changeslide(); // スライド初期値の値を代入して関数実行(初めのスライドに戻す)
      } else {
        slideCurrent++;
        changeslide(); // そうでなければスライド番号を増やして(次のスライドに切り替え)関数実行
      };
    }, 3000); // 上記動作を3秒毎に
  }
  // 「startTimer」関数を止める「stopTimer」関数を定義
  function stopTimer() {
    clearInterval(Timer); // clearInterval・・・setIntervalで設定したタイマーを取り消す
  }
  //////// 自動スライド切り替えタイマーを発動
  startTimer();
    ////////// ボタンを押すとchangeslide関数が発動 //////////
    // NEXTボタン
    $('.js-btn-next').click(function () {
      // 動いているタイマーをストップして再度タイマーを動かし直す(こうしないとページ送り後の時間間隔がズレる)
      stopTimer();
      startTimer();
      if (slideCurrent === lastCurrent) { // 現在のスライドが最終スライドの場合
        slideCurrent = 0;
        changeslide(); // スライド初期値の値を代入して関数実行(初めのスライドに戻す)
      } else {
        slideCurrent++;
        changeslide(); // そうでなければスライド番号を増やして(次のスライドに切り替え)関数実行
      };
    });
    // BACKボタン
    $('.js-btn-back').click(function () {
      // 動いているタイマーをストップして再度タイマーを動かし直す(こうしないとページ送り後の時間間隔がズレる)
      stopTimer();
      startTimer();
      if (slideCurrent === 0) { // 現在のスライドが初期スライドの場合
        slideCurrent = lastCurrent;
        changeslide(); // 最終スライド番号を代入して関数実行(最後のスライドに移動)
      } else {
        slideCurrent--;
        changeslide(); // そうでなければスライド番号を減らして(前のスライドに切り替え)関数実行
      };
    });

これを先ほどのchangeslide関数の下に追記したら完成します。

上が自動フェードインするstopTimer()とstartTimer()の関数定義と実行、下がボタンクリックでの関数呼び出しとなっています。

コードまとめ

最後にコードをまとめておきます!

カルーセルはスライドを横並びにして横方向に動かす、フェードは重ねてfadeInで表示させるという違いがありますが、基本形は同じなので今回は結構省略させてもらいました。

逆に基本形をあるということは使い回しができるということなので、プラグインを使わずにサクッと作りたい時はコピペして試してみてください。

<h1 class="demo-title">クロスフェード デモサイト</h1>
  <ul class="fade-area">
    <!-- 意味的に画像はimgタグで配置してるが、CSSで非表示にする。 -->
    <li class="fade-list"><img class="fade-img" src="img/spring.jpg" alt="春のイメージ"></li>
    <li class="fade-list"><img class="fade-img" src="img/rainyseason.jpg" alt="梅雨のイメージ"></li>
    <li class="fade-list"><img class="fade-img" src="img/summer.jpg" alt="夏のイメージ"></li>
    <li class="fade-list"><img class="fade-img" src="img/autumn.jpg" alt="秋のイメージ"></li>
    <li class="fade-list"><img class="fade-img" src="img/winter.jpg" alt="冬のイメージ"></li>
  </ul>
  <div class="slide-summary">
    <div class="arow-wrap">
      <div class="arrow-left">
        <button class="arrow-btn js-btn-back" type="button"></button>
      </div>
      <div class="arrow-right">
        <button class="arrow-btn js-btn-next" type="button"></button>
      </div>
    </div>
    <div class="pagination">
      <span class="pagination-circle target"></span>
      <span class="pagination-circle"></span>
      <span class="pagination-circle"></span>
      <span class="pagination-circle"></span>
      <span class="pagination-circle"></span>
    </div>
  </div>
/* ベースCSS・リセットCSS */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  list-style: none;
}
button {
  cursor: pointer;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  vertical-align: middle;
  color: inherit;
  font: inherit;
  border: 0;
  background: transparent;
  padding: 0;
  margin: 0;
  outline: none;
  border-radius: 0;
}
.demo-title {
  width: 100%;
  padding: 1em;
  text-align: right;
  position: absolute;
  z-index: 2;
  font-size: 16px;
  color: #fefefe;
}
/*********** ここまでベースCSS・リセットCSS ***********/
.fade-area {
  width: 100%;
  height: 600px;
  position: relative;
}
/* object-fitを使用せず画像トリミングさせるためにbackgroundで指定 */
/* fade-areaに対してposition: absoluteで画像を重ねる */
.fade-list {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}
@media screen and (max-width: 600px) {
  .fade-area {
    height: 300px;
  }
}
.fade-list:nth-child(1) {
  background-image: url(./img/spring.jpg);
}
.fade-list:nth-child(2) {
  background-image: url(./img/rainyseason.jpg);
}
.fade-list:nth-child(3) {
  background-image: url(./img/summer.jpg);
}
.fade-list:nth-child(4) {
  background-image: url(./img/autumn.jpg);
}
.fade-list:nth-child(5) {
  background-image: url(./img/winter.jpg);
}
/* imgタグで画像指定したかったためclipで非表示指定(スクリーンリーダー対策) */
.fade-img {
  width: 1px;
  height: 1px;
  clip: rect(1px, 1px, 1px, 1px);
  -webkit-clip-path: inset(50%);
  clip-path: inset(50%);
  margin: -1px;
  padding: 0;
  overflow: hidden;
  position: absolute;
}
/*********** スライド補助エリア ***********/
.slide-summary {
  max-width: 600px;
  margin: 0 auto;
  padding: 16px;
  display: flex;
  justify-content: space-around;
  align-items: center;
}
/*********** スライド送りボタン ***********/
/* 共有パーツ */
.arow-wrap {
  width: 140px;
  display: flex;
  justify-content: space-between;
}
.arrow-btn {
  width: 48px;
  height: 48px;
  background-color: rgb(113, 135, 245);
  border-radius: 5%;
  transition: .2s;
}
.arrow-btn:focus {
  box-shadow: 0px 1px 10px -2px rgba(0, 0, 0, 0.8);
}
/* プライマリー入力ディバイスがhoverを使える場合 */
@media (hover: hover) {
  .arrow-btn:hover {
    background-color: rgb(51, 79, 216);
    box-shadow: 0px 1px 10px -2px rgba(0, 0, 0, 0.8);
  }
}
/* 左 */
.arrow-left {
  position: relative
}
.arrow-left:before {
  content: "";
  width: 10px;
  height: 10px;
  border-top: 2px solid #fefefe;
  border-left: 2px solid #fefefe;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-30%, -50%)rotate(-45deg);
}
/* 右 */
.arrow-right {
  position: relative
}
.arrow-right:before {
  content: "";
  width: 10px;
  height: 10px;
  border-top: 2px solid #fefefe;
  border-left: 2px solid #fefefe;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-70%, -50%)rotate(135deg);
}
@media screen and (max-width: 600px) {
  .slide-auxiliary {
    width: 90%;
  }
  .arow-wrap {
    width: 80px;
  }
  .arrow-btn {
    width: 30px;
    height: 30px;
  }
}
/*********** ページネーション ***********/
.pagination {
  width: 150px;
  display: flex;
  justify-content: space-around;
}
.pagination-circle {
  width: 20px;
  height: 20px;
  border-radius: 5%;
  background-color: rgba(83, 97, 223, 0.3);
  position: relative;
}
/* jsでtargetクラスがついたら背景色を変える */
.pagination-circle.target::before {
  content: "";
  width: 10px;
  height: 10px;
  display: block;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background-color: rgba(83, 97, 223, 0.8);
}
// 自動フェードインで切り替わるスライドパターン
$(function () {
  ////////// スライドの現在値と最終スライド番号を変数化 //////////
  var slideCurrent = 0; // スライド現在値(1枚目のスライド番号としての意味も含む)
  var lastCurrent = $('.fade-list').length - 1; // スライドの合計数=最後のスライド番号
  //////// 初期スライドだけ表示 //////////
  $('.fade-list').css('display', 'none'); // 一旦すべてのスライドを非表示
  $('.fade-list').eq(slideCurrent).css('display', 'block'); // 最初のスライドを表示
  ////////// スライドの切り替わりを「changeslide」として定義(ボタンで取得したスライド番号で変化させる) //////////
  function changeslide() {
    $('.fade-list').fadeOut(1000); // 現在のスライドをフェードアウトさせる
    $('.fade-list').eq(slideCurrent).fadeIn(1500); // 次のスライドを表示させる
    //////// 現在のページに合わせてページネーションを変化させる
    var pagiNation = slideCurrent + 1; // nth-of-typeで1枚目もカウントとるためスライド数を修正して変数代入
    // targetクラスを削除
    $('.pagination-circle').removeClass('target');
    // 現在のボタンにtargetクラスを追加
    $(".pagination-circle:nth-of-type(" + pagiNation + ")").addClass('target');
  };
  //////// 自動スライド切り替えのタイマーを設定 //////////
  var Timer;
  // 一定時間毎に処理実行する「startTimer」関数を定義
  function startTimer() {
    Timer = setInterval(function () { // setInterval・・・指定した時間ごとに関数を実行
      if (slideCurrent === lastCurrent) { // 現在のスライドが最終スライドの場合
        slideCurrent = 0;
        changeslide(); // スライド初期値の値を代入して関数実行(初めのスライドに戻す)
      } else {
        slideCurrent++;
        changeslide(); // そうでなければスライド番号を増やして(次のスライドに切り替え)関数実行
      };
    }, 3000); // 上記動作を3秒毎に
  }
  // 「startTimer」関数を止める「stopTimer」関数を定義
  function stopTimer() {
    clearInterval(Timer); // clearInterval・・・setIntervalで設定したタイマーを取り消す
  }
  //////// 自動スライド切り替えタイマーを発動
  startTimer();
  ////////// ボタンを押すとchangeslide関数が発動 //////////
  // NEXTボタン
  $('.js-btn-next').click(function () {
    // 動いているタイマーをストップして再度タイマーを動かし直す(こうしないとページ送り後の時間間隔がズレる)
    stopTimer();
    startTimer();
    if (slideCurrent === lastCurrent) { // 現在のスライドが最終スライドの場合
      slideCurrent = 0;
      changeslide(); // スライド初期値の値を代入して関数実行(初めのスライドに戻す)
    } else {
      slideCurrent++;
      changeslide(); // そうでなければスライド番号を増やして(次のスライドに切り替え)関数実行
    };
  });
  // BACKボタン
  $('.js-btn-back').click(function () {
    // 動いているタイマーをストップして再度タイマーを動かし直す(こうしないとページ送り後の時間間隔がズレる)
    stopTimer();
    startTimer();
    if (slideCurrent === 0) { // 現在のスライドが初期スライドの場合
      slideCurrent = lastCurrent;
      changeslide(); // 最終スライド番号を代入して関数実行(最後のスライドに移動)
    } else {
      slideCurrent--;
      changeslide(); // そうでなければスライド番号を減らして(前のスライドに切り替え)関数実行
    };
  });
});

まとめ

今回はjQueryを使ったクロースフェードのスライドショー実装方法をまとめてみました。

Webサイトのファーストビューなど、かなりたくさんの場面で採用されるデザインなので、サクッと実装できるように準備しておきましょう!

他にもjQueryを活用したデザインパターンも色々と解説しているのでチェックしてみてくださいね!


当記事を読まれている方の中にはWeb制作初学者の方もいるかと思います。デザインやコーディングの基礎知識を学びたい方向けの記事を用意しているので是非見ていってください!

PENGIN無料コーディング課題

オススメ書籍紹介

オススメUdemy講座紹介