カルーセルスライダーの実装 〜スライド編〜
目的
改めて作成の流れを整理したいと思いこちらの記事を作成いたしました。
クオリティは低いですが、最後まで見てくださると嬉しいです。
作成までの流れ
まず完成形を確認する!
スライドを実装していく!
インジケーターを実装していく!
前後に進む矢印を実装していく!
全コード
まず完成形を確認する!
要件は下記の通りです
・1000ms ごとに自動で右の画像にスライドする
・最後のスライドの後に右にスライドした時、最初のスライドが表示される
・矢印を押すと画像が矢印の方向にスライドする
・現在表示されてる画像のインジケーターの色が白になる
・インジケーターを押したら指定のスライドが表示される
1. 機能を洗い出す
まずはどんな機能が備わっているか改めて言葉にします!
A. 自動で画像が左にスライドしている
B. 1秒ごとにスライドしている
C. サイドの画像が表示されたら1枚目の画像に戻る
D. 表示されている画像の丸ぽちが白くなる
E. 下の丸ぽちを押したらその画像が表示される
F. 矢印を押すと進む/戻る
2. 機能をグループ化
機能を洗い出したら今度はグループ化しましょう!
・画像のスライド
A、B、C
・丸ぽち
D、E
・矢印
F
このように3つのグループに分けることができました。
3. 実装順番を決める
上記で3つに分けることができましたが、次は何から手をつけていくか決めます。
「丸ぽち」と「矢印」については画像がスライドしていないと確認のしようがないので、まずは
・A、B、C
そして矢印を実装させるときは「丸ぽち」が動いているかも確認をしたいので2番目に
・D、E
最後に
・F
という順番で実装したいと思います。
スライドを実装していく!
1. 画像をスライドさせる
まず上記のように大枠を作り、
画像(absolute,flex等を使いながら)をはめていきます。
白い部分は親要素に設定したborderです!
(スライドがわかりやすいようにoverflow: hiddenは後で使用)
では「画像一枚分」を左に動かしたいので、left:-100% を指定してあげれば良さそうです!
※なぜ 100% で画像一枚分をスライドできるのかは、こちらの記事を確認してください。
スライドさせるためのコードを書いていきます!
const duration = 1000;
let currentIndex = 0;
function changeSlide() {
$slides.animate({ //$slidesにアニメーションを与える
left: (currentIndex) * -100 + '%' //leftに-100%を与える
}, duration); //1秒かけて
}
function goNextSlide() {
currentIndex++; //変数currentIndexを1増やす
changeSlide(); //changeSlide()を実行する
}
function startTimer() {
timer = setInterval(goNextSlide, duration); //1秒ごとにstartTimer()を実行する
}
startTimer();
changeSlide関数(スライドさせる大元の関数で、「1秒かけて、leftに-100%を与える」という意味)
・$slides(画像をまとめている要素)にanimateを指定してcssを適用させる
・currentIndex → 初期値に0が設定されている(この数に応じて、left値が決まる)
goNextSlide関数
・「++」でcurrentIndexを1ずつ増やして、changeSlide()を実行する
※なぜ「currentIndex++」をchangeSlide()に記述しないかというと、後ほど実装する「矢印」の時にも役立つからです。
startTimer関数
・setIntervalを使って、goNextSlide()を1秒ごと繰り返す
2. スライドをループさせる
前述でスライドが実装できましたが、このままでは永遠にleftにスライドし続けてしまいます・・・。
最後の画像が表示されたら1枚目に戻したいので、if文(もし最後の画像が表示されたら〜)を書いていきたいと思います!
const $imageNumber = $slide.length;
const duration = 1000;
let currentIndex = 0;
function changeSlide() {
$slides.animate({
left: (currentIndex ) * -100 + '%'
}, duration);
if (currentIndex == $imageNumber - 1) {
currentIndex = 0;
$slides.animate({
left: (currentIndex) * -100 + '%'//この記述で1枚目に戻る
}, 0);
}
}
changeSlide関数
if文を追加しました。
「もしcurrentIndexが(画像枚数 - 1)と一致したら、currentIndexを0に戻してleft値を変える」
という記述です。
※なぜ「-1」をするのかというと画像枚数は1から数え始めるが、「index = currentIndex」は0からスタートするためです。
このように一枚目に戻すことができました!
ただ1枚目に戻るときに不自然な動きになってしまいました。
上記画像のように、最初の画像が最後にもあれば滑らかにスライドしそうです!
これを実現させるために「cloneプロパティ」を使いたいと思います。
※HTMLに記述しない理由は2つあります。
・本来は記述が必要のない画像ということ
・画像が増えたときに書き換えないといけないため
それでは記述していきます!
function cloneItem() {
const $firstSlide = $slides.find('li:first-child');
$firstSlide.clone().appendTo($slides);
}
function changeSlide() {
$slides.animate({
left: (currentIndex ) * -100 + '%'
}, duration);
if (currentIndex == $imageNumber) { //1枚追加されたので「−1」を削除します
currentIndex = 0;
$slides.animate({
left: (currentIndex) * -100 + '%'
}, 0);
}
}
//その他省略
function init() {
cloneItem();
startTimer();
}
init();
cloneItem()
1行目で、「画像の1枚目を取得」
2行目で、firstSlideをクローンし、slidesの最後に追加
changeSlide()
「$imageNumber -1 」の-1を削除。
init()
実行関数が増えたので、一つの関数にまとめた方が管理しやすい
するとどうでしょう!滑らかにスライドしました!
※最後にoverflow: hiddenを設定して見た目を調節してあげたら、次の課題に行きたいと思います!
インジケーターを実装していく!
1. インジケーターの追加
内容を確認したいと思います。
・画像枚数分のインジケーターを設置
・画像が増えたら、自動でインジケーターが増える
・現在表示されてる画像のインジケーターの色が白になる
・インジケーターを押したら指定のスライドが表示される
直接HTMLにインジケーター用の要素を記述すると、画像が増えたときにインジケーターが自動で生成されないので、jQueryから追加したいと思います!
function createIndicator() {
for (let currentIndex = 0; currentIndex < $imageNumber; currentIndex++) {
$indicators.append('<li class="indicator"></li>');
}
}
function cloneItem() {
//省略
}
for文を使って「<li class="indicator"></li>
の作成を、画像枚数と同じ回数繰り返す」という記述になります。
このように記述することで画像が突如増えても、インジケーターが勝手に追加されます。
(cssの記述は、各自事前にお願いいたします!)
2. インジケーターの色の変更
続いて「現在表示されてる画像のインジケーターの色が白になる」を実装したいと思います。
function createIndicator() {
for (let currentIndex = 0; currentIndex < $imageNumber; currentIndex++) {
$indicators.append('<li class="indicator"></li>');
}
$indicators.find('li:first-child').eq(currentIndex).addClass('is-active');//こちらを追加
}
//以下省略
function startTimer() {
}
//こちらも追加
function changeIndicator() {
$indicators.find('li').removeClass("is-active").eq(currentIndex).addClass("is-active");
}
※is-active
に白くなるcssを記述してあるため、クラス名の位置を変更するコードを書いてあげます。
createIndicator()
「$indicatorsの子要素である全てのliのクラス名を削除し、currentIndex番目のliにクラス名is-activeを付与する」という記述。
changeIndicator()
対象全てのli
のis-active
クラスを削除し、eq(currentIndex)
番目のli
にis-active
を追加する
そしてcreateIndicator()を、
function changeSlide() {
//省略
createIndicator();
}
このようにchangeSlide()の中で呼び出すことにより、画像のスライドと同じように1秒ごとに実行されます。
このように、ボタンを押すと対象の画像が表示されるようになりました!
2. インジケーターを押したら、対象の画像が表示される
続いて「インジケーターをクリックしたら対象の画像が表示される」という機能を実装していきますが、
画像及びインジケーターはcurrentIndex
をもとに何番目かを判断しています。
↓
例:currentIndex = 0
なら0番目の画像、currentIndex = 1
なら1番目の画像
つまり、
「インジケーターをクリックしたらcurrentIndex
の番号が変わる」 = 「画像が変わる」ということなので、それを実現させるコードを記述していきます。
function goSomewhereIndicator() {
if (!$slides.is(':animated')) { //もしアニメーションが実行されていなかったら
currentIndex = $(this).index();
changeSlide();
}
}
function setEvent() {
$indicators.find('.indicator').on('click', goSomewhereIndicator);
}
function init() {
createIndicator();
cloneItem();
setEvent();
startTimer();//startTimer()の呼び出し位置を変更
}
init();
goSomewhereIndicator()
・currentIndex = $(this).index()
→クリックした要素自体のindexをcurrentIndex
に代入する
・代入されたcurrentIndex
に基づいてchangeSlide()を実行させる
setEvent()
・イベント用の発火関数
このように、ボタンを押すと対象の画像が表示されるようになりました!
前後に進む矢印を実装していく!
最後に矢印(HTML&CSSは記述済みとする)を作っていきます!
どうすればスライドが前後に動くかというと、currentIndex
が1増える or 1減ることで画像が前後に動きそうです。
よって「矢印をクリックして、currentIndex
が1増やす or 1減らしてから、changeSlide()を実行する」というコードを記述します。
function goNextSlide() {
if (!$slides.is(':animated')) {//もしアニメーションが実行されていなかったら
currentIndex++;
changeSlide();
}
}
function prevSlide() {
if (!$slides.is(':animated')) {//もしアニメーションが実行されていなかったら
currentIndex--;
changeSlide();
}
}
function setEvent() {
$buttonWrap.find('.prev').on('click', prevSlide);
$buttonWrap.find('.next').on('click', goNextSlide);
$indicators.find('.indicator').on('click', goSomewhereIndicator);
}
goNextSlide()
もしかしたらお気づきかもしれませんが、goNextSlide()
はすでに「スライドを実装していく!」で定義している関数です。
currentIndex
を1増やしてchangeSlide()
を発火させるのは矢印の実装でも役に立つので、そのままクリックイベントを付与しました。
prevSlide()
逆に一つ後ろの画像に戻すためにはcurrentIndex
を1減らせばいいので、ほぼ同じ形の
「矢印をクリックしたらcurrentIndex
を1減らして、changeSlide()
を発火させる」という関数を用意しました。
しかし!!
一つ後ろの画像に戻すprevSlide()
で新たな問題が発生しました。それは一番最初の画像から一つ戻す矢印を押すと、画像が何も表示されないという現象です。
詳しくいうと、左に画像なんてないのにどんどん右にスライドしてしまっているのです。
そこでスライドをループさせるという項目でやった通り、最後の画像クローンを1枚目としてセットすれば解決しそうです!
それでは下記コードを記述していきます。
※1枚目の画像が変わるので、cssでleft:-100%
を記述しておいてください!そうすることで一番最初に、2枚目の画像(実質1枚目の画像)が表示されます。
function cloneItem() {
const $firstSlide = $slides.find('li:first-child');
const $lastSlide = $slides.find('li:last-child');//追加
$firstSlide.clone().appendTo($slides);
$lastSlide.clone().prependTo($slides);//追加
}
function changeSlide() {
$slides.animate({
left: (currentIndex + 1) * -100 + '%'//「+1」を追加
}, duration);
if (currentIndex == $imageNumber) {
currentIndex = 0;
$slides.animate({
left: (currentIndex + 1) * -100 + '%'//「+1」を追加
}, 0);
} else if (currentIndex == -1) {//else ifを追加
currentIndex = $imageNumber - 1;
$slides.animate({
left: (currentIndex + 1) * -100 + '%'
}, 0);
}
changeIndicator();
}
cloneItem()
「最後の画像をクローンして、一番最初に設置」させるために2行を追加で記述しました。
changeSlide()
こちらの関数に2点追加しました。
①まず「+1」を記述した理由は、cloneItem()
により、2枚目の画像が実質1枚目となるためです。
②そしてelse if
ですが、
「もしcurrentIndex
が-1になったら、最後の画像(「$imageNumber - 1」番目の画像)を表示させる」
という意味になります。
そうすると、
1枚目の状態で前に戻る矢印を押すと最後の画像が表示されるようになり、
スライダーを完成させることができました!!!
下記のようにスライドもインジケーターも矢印もしっかり機能しています!!
全コード
少しごちゃごちゃしてしまったので、全体コードを記述しておきます!
$(function () {
function slider() {
const $slider = $('#slider');
const $slideWrap = $slider.find('.slideWrap');
const $slides = $slideWrap.find('.slides');
const $slide = $slides.find('.slide');
const $buttonWrap = $slideWrap.find('.buttonWrap');
const $indicators = $slider.find('.indicators');
const $imageNumber = $slide.length;
console.log($imageNumber)
const duration = 1000;
let currentIndex = 0;
let timer = '';
//上記は詳しく記述はしておりませんので、各自マークアップごとに変更を加えてください
function createIndicator() {
for (let currentIndex = 0; currentIndex < $imageNumber; currentIndex++) {
$indicators.append('<li class="indicator"></li>');
}
$indicators.find('li:first-child').eq(currentIndex).addClass('is-active');
}
function cloneItem() {
const $firstSlide = $slides.find('li:first-child');
const $lastSlide = $slides.find('li:last-child');
$firstSlide.clone().appendTo($slides);
$lastSlide.clone().prependTo($slides);
}
function changeSlide() {
$slides.animate({
left: (currentIndex + 1) * -100 + '%'
}, duration);
if (currentIndex == $imageNumber) {
currentIndex = 0;
$slides.animate({
left: (currentIndex + 1) * -100 + '%'
}, 0);
} else if (currentIndex == -1) {
currentIndex = $imageNumber - 1;
$slides.animate({
left: (currentIndex + 1) * -100 + '%'
}, 0);
}
changeIndicator();
}
function goNextSlide() {
if (!$slides.is(':animated')) {
currentIndex++;
changeSlide();
}
}
function prevSlide() {
if (!$slides.is(':animated')) {
currentIndex--;
changeSlide();
}
}
function startTimer() {
timer = setInterval(goNextSlide, duration);
}
function changeIndicator() {
$indicators.find('li').removeClass("is-active").eq(currentIndex).addClass("is-active");
}
function goSomewhereIndicator() {
if (!$slides.is(':animated')) {
currentIndex = $(this).index();
changeSlide();
}
}
function setEvent() {
$indicators.find('.indicator').on('click', goSomewhereIndicator);
$buttonWrap.find('.prev').on('click', prevSlide);
$buttonWrap.find('.next').on('click', goNextSlide);
}
function init() {
createIndicator();
cloneItem();
setEvent();
startTimer();
}
init();
}
slider();
});
まとめ
今回のスライダー作成はかなりつまづきました。各機能を実装する時はもちろんですが、個人的に一番つまづいたのは、そもそものどうやって・どの順番で、実装していくかの部分です。
ですのでまず完成形を確認する!の考え方が伝わって欲しいなと思っています。
・どんな機能が備わっているのか
↓
・共通する機能は何か
↓
・何から実装していくか
ここをしっかり押さえていけば、やりたいことははっきりしていくかなと思います!
ご精読ありがとうございました!!