1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

簡単!FlexBoxで作るカルーセルスライダー

Last updated at Posted at 2022-03-21

カルーセルとは?

大体こういうやつです。
皆様もLPのレヴューセクションなどで一度や二度は目にしたことがあるのではないでしょうか?
image.png
左右のボタンを押すとレヴューがその方向にヒュッ!とスライドし、他のレヴューが現れます。
image.png

よく見ると下の白い丸が点灯し、何番目のレビューかが分かるようにもなっています。
こういうのカッコいいですよね。
現物はこちらのCodePenからご覧になれます。

結局どうやってるの?

原理的には至ってシンプルです。
まず、FlexBoxを使ってレヴューを横に並べるのですが、この際

  • レヴューの横幅を親要素と同じまで広げる
  • 親要素にoverflow:hiddenを設定する

ことで、選択されていないレヴューを画面の外に追い出すことができます。
image.png
表示するレヴューを変更する方法もシンプルです。
レヴュー全体にtransform:translateX(-100%)を加えれば、全体が左に動き、最初のレヴューの右側にあったレヴューが親要素に入り、見えるようになります。
image.png
transform:translateX(100%)に書き換えれば、逆に右に動き、一つ左のレヴューが見えるようになります。
image.png
左右のボタンを押したらこの処理を実行するよう、JavaScriptを組めばいいだけです。

HTML & CSS

それでは、今言ったことを実装していきます。
レイアウトを実現するにはいくつか方法がありますが、今回はflexboxを使用します。垂直方向のflexboxの中に、水平方向のflexboxを入れ子にします。
image.png
レヴューを画面幅まで広げるには以下のようなレイアウトを作ります。
ポイントは、

  • 左ボタン、3つを入れるコンテナ、右ボタンをflexで並べる
  • コンテナをflex:1 1 autoに設定する。これで左ボタン+コンテナ+右ボタンの長さが画面幅と同じになる
  • コンテナ内にレヴュー3つをflexで並べる。
  • レヴューにflex:1 0 100%を設定する。これでレヴューはコンテナが同じ幅まで広がり、ひとつのレヴューのみがコンテナ内に収まる
  • コンテナにoverflow:hiddenを設定し、コンテナからはみ出している2つのレヴューを表示されないようにする

ことです。
image.png

index.html
<head>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.0/css/all.min.css" integrity="sha512-10/jx2EXwxxWqCLX/hHth/vu2KY3jCF70dCQB8TSgNjbCVAC/8vai53GfMDrO2Emgwccf2pJqxct9ehpzG+MTw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>

<body>

  <div class="vertical-container">
    <div class="horizontail-container">
      <button class="btn" id="prev">
        <i class="fa-solid fa-angle-left fa-xl"></i>
      </button>
      <div class="reviews-container">
        <div class="review">
          <div class="review-image"></div>
          <p class="review-content">
            これはとても素晴らしい商品です。あまりにも素晴らしすぎてなんかもうビックリしました。
          </p>
          <h3 class="review-name">
            田中 太郎
          </h3>
        </div>
        <div class="review">
          <div class="review-image"></div>
          <p class="review-content">
            みんな「すごい」「ヤバい」って言ってるけどこれそんなに凄いか? ぶっちゃけステマじゃね?
          </p>
          <h3 class="review-name">
            鈴木 次郎
          </h3>
        </div>
        <div class="review">
          <div class="review-image"></div>
          <p class="review-content">
            This product is..absolutely awesome, far beyond my expectation. Everyone should buy this!!!
          </p>
          <h3 class="review-name">
            John Smith
          </h3>
        </div>
      </div>
      <button class="btn" id="next"> <i class="fa-solid fa-angle-right fa-xl"></i></button>
    </div>
    <div class="indicator-container">
      <div class="indicator active"></div>
      <div class="indicator"></div>
      <div class="indicator"></div>
    </div>
  </div>
</body>
style.css
h3,
p {
  margin: 0;
}
.vertical-container {
  display: flex;
  flex-direction: column;
  margin: 15px;
}
.horizontail-container {
  display: flex;
  gap: 15px;
  align-items: center;
}
.reviews-container {
  flex: 1 1 auto;
  display: flex;
  overflow: hidden;
}
.review {
  flex: 1 0 100%;
  text-align: center;
  margin: 15px 0 25px;
  display: grid;
  place-items: center;
  gap: 15px;
  transition-duration: 0.5s;
}
.review-image {
  width: 75px;
  height: 75px;
  background-color: #778899;
  border-radius: 50%;
}
.indicator-container {
  display: flex;
  justify-content: center;
  gap: 25px;
}
.indicator {
  height: 15px;
  width: 15px;
  border-radius: 50%;
  border: solid 1px gray;
}
.btn {
  z-index: 10;
  background-color: transparent;
  border: none;
  cursor: pointer;
}

これだけで、相当それっぽい画面が出来たはずです。
あとはJavaScriptを組むだけです。

JavaScript

スライドを実装するための戦略は以下の通りです。

  • ボタンとレヴュー3つをquerySelectorquerySelectorAllで拾う
slider.js
const btns = document.querySelectorAll(".btn");
const reviews = document.querySelectorAll(".review");
  • 現在表示するレヴューを表すインデックスをletで実装する
slider.js
let currentIdx = 0;
  • 左右のボタンを押すと、以下二つのことが起きるようにする。
  1. 押したボタンに合わせてインデックスが増減するようにする。ただしインデックスが0より小さく成ったり、レビューの数-1より大きくならないように条件分岐を設定する。
  2. 3つのレヴュー全てのtransform: translateXの値を、インデックス数 × 100%に変更する。
  • また、ボタンクリックイベント中に重ねてボタンをクリックしても無効になるよう、イベント実行中であることを表す変数isMovingをletで実装する。
slider.js
//レヴュー3つををスライドする
const moveSlider = () => {
  reviews.forEach((el) => {
    el.style.transform = `translateX(-${currentIdx * 100}%)`;
  });
};
const handleButtonClick = (e) => {
  if (!isMoving) {
    isMoving = true;
    if (e.currentTarget.id === "prev" && currentIdx > 0) {
      currentIdx--;
    } else if (
      e.currentTarget.id === "next" &&
      currentIdx < reviews.length - 1
    ) {
      currentIdx++;
    }
    moveSlider();
    isMoving = false;
  }
};
btns.forEach((btn) => {
  btn.addEventListener("click", handleButtonClick);
});

また、下の白い丸の点灯は以下のようにして実現します。

  • ボタンが白くなるのはCSSのクラスactiveで表現する。
style.css
.active {
  background-color: gray;
}
  • 現在インデックスが差しているレヴューのみにactiveクラスを付与する(ほかのレヴューからは消去する)関数を作る。
slider.js
const moveIndicator = () => {
  indicators.forEach((el) => {
    el.classList.remove("active");
  });
  indicators[currentIdx].classList.add("active");
};
  • ボタンクリックの中で、その関数を実行する。
slider.js
const handleButtonClick = (e) => {
  if (!isMoving) {
    isMoving = true;
    if (e.currentTarget.id === "prev" && currentIdx > 0) {
      currentIdx--;
    } else if (
      e.currentTarget.id === "next" &&
      currentIdx < reviews.length - 1
    ) {
      currentIdx++;
    }
    moveSlider();
    moveIndicator();
    isMoving = false;
  }
};

これで動くようになりました。

所感

作ってる時はシンプルだと感じたのですが、言葉で説明するとあまり簡単ではない気がしてきました……。

参考

Build a Static Site with Figma & Astro #8 - Infinite Slider

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?