Blog
初心者でもできる!純粋なJavaScriptで自作スライドショーを作る方法【修正版】
前回のスライドショーには、実は少しだけ気になる点があったんです…。それは、「最後のスライドから最初のスライドに戻る時、一瞬パッと切り替わる様子が見えてしまって、ちょっと不自然だな…」という点でした。
もしかしたら、あなたもそう感じたかもしれませんね。
そこで!今回は、その「最後のループの不自然さ」を解消するために、コードをバッチリ修正しました!🎉
この【修正版】なら、もっと滑らかに、そして自然にスライドがループしますよ!
「純粋なJavaScriptで、もっとクオリティの高いスライドショーを作ってみたい!」という初心者さんのために、パワーアップした作り方を、今回もわかりやすく丁寧にご紹介していきます。
さあ、一緒にステップアップしましょう!
<article class="top-01a">
<section id="tslide" class="tslide">
<div>
<p>スライド1</p>
</div>
<div>
<p>スライド2</p>
</div>
<div>
<p>スライド3</p>
</div>
<div>
<p>スライド4</p>
</div>
</section>
<!-- 左右のボタン -->
<span id="prev" class="prev"></span>
<span id="next" class="next"></span>
<!-- インジケーター -->
<ul class="indicator" id="indicator">
<li class="list"></li>
<li class="list"></li>
<li class="list"></li>
<li class="list"></li>
</ul>
</article>
.top-01a {
position: relative;
width: 100%;
height: 400px;
margin: 0 auto;
overflow: hidden;
}
.tslide {
position: relative;
width: 100%;
height: 100%;
}
.tslide div {
position: absolute;
width: 100%;
height: 100%;
opacity: 0;
transition: opacity 0.5s ease-in-out;
display: flex;
align-items: center;
justify-content: center;
background-color: #f0f0f0;
}
.tslide div:nth-of-type(1) {
background-image: url(img/top1.png);
background-size: cover;
background-position: right;
}
.tslide div:nth-of-type(2) {
background-image: url(img/top2.png);
background-size: cover;
}
.tslide div:nth-of-type(3) {
background-color: #E3F1E4;
}
.tslide div:nth-of-type(4) {
background-color: #b5e7f8;
}
.tslide div p {
font-size: 24px;
color: #333;
}
.tslide div.active {
opacity: 1;
}
.prev,
.next {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 40px;
height: 40px;
background-color: rgba(255, 255, 255, 0.7);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: background-color 0.3s;
}
.prev:hover,
.next:hover {
background-color: rgba(255, 255, 255, 0.9);
}
.prev::before,
.next::before {
content: '';
width: 10px;
height: 10px;
border-top: 2px solid #333;
border-right: 2px solid #333;
display: inline-block;
}
.prev {
left: 20px;
}
.prev::before {
transform: rotate(-135deg);
}
.next {
right: 20px;
}
.next::before {
transform: rotate(45deg);
}
.indicator {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 10px;
margin: 0;
padding: 0;
list-style: none;
}
.list {
width: 12px;
height: 12px;
background-color: #fff;
border-radius: 50%;
cursor: pointer;
transition: background-color 0.3s, transform 0.3s;
}
.list:hover {
transform: scale(1.2);
}
.list.active {
background-color: #000;
}
document.addEventListener('DOMContentLoaded', () => {
const slide = document.getElementById('tslide');
const slides = slide.querySelectorAll('div');
const prev = document.getElementById('prev');
const next = document.getElementById('next');
const indicator = document.getElementById('indicator');
const lists = document.querySelectorAll('.list');
const totalSlides = slides.length;
let currentSlide = 0;
let isTransitioning = false;
let autoPlayInterval;
// Initialize first slide and indicator
slides[0].classList.add('active');
lists[0].classList.add('active');
function updateSlides(newIndex) {
if (isTransitioning) return;
isTransitioning = true;
// Remove active class from current slide and indicator
slides[currentSlide].classList.remove('active');
lists[currentSlide].classList.remove('active');
// Update current slide index
currentSlide = (newIndex + totalSlides) % totalSlides;
// Add active class to new slide and indicator
slides[currentSlide].classList.add('active');
lists[currentSlide].classList.add('active');
// Reset transition lock after animation completes
setTimeout(() => {
isTransitioning = false;
}, 500);
}
function nextSlide() {
updateSlides(currentSlide + 1);
}
function prevSlide() {
updateSlides(currentSlide - 1);
}
function goToSlide(index) {
if (index === currentSlide) return;
updateSlides(index);
}
function startAutoPlay() {
autoPlayInterval = setInterval(nextSlide, 5000);
}
function resetAutoPlayInterval() {
clearInterval(autoPlayInterval);
startAutoPlay();
}
// Event Listeners
next.addEventListener('click', () => {
nextSlide();
resetAutoPlayInterval();
});
prev.addEventListener('click', () => {
prevSlide();
resetAutoPlayInterval();
});
indicator.addEventListener('click', (event) => {
if (event.target.classList.contains('list')) {
const index = Array.from(lists).indexOf(event.target);
goToSlide(index);
resetAutoPlayInterval();
}
});
// Start autoplay
startAutoPlay();
});
コードを parts ごとに見ていこう!
それでは、実際のコードの中身を一緒に見ていきましょう。長いコードに見えるかもしれませんが、一つ一つの役割を理解すれば大丈夫ですよ!
document.addEventListener('DOMContentLoaded', () => {
// ここにスライドショーを動かすためのコードが入ります
});
まず、コード全体が document.addEventListener('DOMContentLoaded', () => { ... });
で囲まれています。これは、「HTMLのページが読み込まれて、操作できるようになってから、中のコードを実行しますよ」という意味です。こうすることで、「まだHTMLがないのに、要素を探そうとしてエラーになる」といったことを防げます。安心安全なおまじないだと思ってください。
const slide = document.getElementById('tslide'); // スライド全体を囲む要素
const slides = slide.querySelectorAll('div'); // 個々のスライド要素(表示したい画像などの<div>)
const prev = document.getElementById('prev'); // 「前へ」ボタン
const next = document.getElementById('next'); // 「次へ」ボタン
const indicator = document.getElementById('indicator'); // インジケーター全体を囲む要素
const lists = document.querySelectorAll('.list'); // 個々のインジケーター要素(点の<li>や<a>など)
const totalSlides = slides.length; // スライドの総数を数えて変数に入れる
let currentSlide = 0; // 現在表示しているスライドが何番目かを記録する変数(最初は0番目)
let isTransitioning = false; // アニメーション中かどうかのフラグ(最初はアニメーション中ではないのでfalse)★修正版で追加!
let autoPlayInterval; // 自動再生用のタイマーをしまっておく変数
ここでは、HTMLで準備したスライドショーの各部分(スライド本体、ボタン、インジケーター)をJavaScriptで操作できるように、「参照(しょっとく)」しています。それぞれの変数に、対応するHTML要素が入っています。
totalSlides
は slides
の数(つまりスライドの合計枚数)を自動で数えています。
currentSlide
は、今「何枚目のスライドが表示されているか」を覚えておくための大切な変数です。最初は 0
になっていますが、これはプログラミングの世界では「最初のもの」を 0
と数えることが多いためです。(1枚目なら0、2枚目なら1、となります)
isTransitioning
は、今回の【修正版】で追加された、アニメーションがスムーズに進むためのガードマンです。スライドが切り替わるアニメーションの最中に、うっかり次の操作をしてしまっても、表示が崩れないように「今、アニメーション中だから待ってね!」という状態を記録します。最初はアニメーションは始まっていないので false
です。
autoPlayInterval
は、後で設定する「5秒ごとに自動で次のスライドに切り替える」というタイマーを管理するために使います。
// Initialize first slide and indicator
slides[0].classList.add('active'); // 最初のスライドに'active'クラスを付ける
lists[0].classList.add('active'); // 最初のインジケーターにも'active'クラスを付ける
JavaScriptが動き出したら、まず最初に「最初のスライド」と「最初のインジケーター」に active
というクラスを付けています。これは、**CSS側で.active
というクラスが付いている要素だけを表示する(または特別なスタイルを適用する)**ように設定しておけば、ページを開いたときに最初のスライドがちゃんと見えるようにするためです。
スライドを切り替える心臓部
function updateSlides(newIndex) {
if (isTransitioning) return; // アニメーション中なら何もしないで処理を終える
isTransitioning = true; // これからアニメーションが始まるのでフラグをtrueにする
// Remove active class from current slide and indicator
slides[currentSlide].classList.remove('active'); // 今のスライドの'active'クラスを外す(非表示にする)
lists[currentSlide].classList.remove('active'); // 今のインジケーターの'active'クラスを外す
// Update current slide index
currentSlide = (newIndex + totalSlides) % totalSlides; // 次に表示するスライド番号を計算する(% totalSlidesで最後の次は最初に戻る)
// Add active class to new slide and indicator
slides[currentSlide].classList.add('active'); // 新しいスライドに'active'クラスを付ける(表示する)
lists[currentSlide].classList.add('active'); // 新しいインジケーターに'active'クラスを付ける
// Reset transition lock after animation completes
setTimeout(() => { //
isTransitioning = false; // 500ミリ秒後にフラグをfalseに戻す
}, 500); // ★修正版で追加!
}
この updateSlides
関数が、スライドを切り替えるための最も重要な部分です。
新しいスライド番号 newIndex
を受け取って、表示を切り替えます。
- まず
if (isTransitioning) return;
の行(修正版で追加!)で、もし今アニメーション中なら、これ以上進まずに関数を終了します。これが連続操作を防ぐガードマンの役割です。 - アニメーションが始まるときに
isTransitioning = true;
とフラグを立てます。 - 今表示されているスライドとインジケーターから
active
クラスを外します。(これで今までのスライドが非表示になります) newIndex
を使って、次に表示するスライド番号currentSlide
を計算します。ここで% totalSlides
(モジュロ演算子)を使っているおかげで、例えばスライドが3枚あって (totalSlides=3)、0, 1, 2 と進んだ後、次に3
になろうとしても3 % 3
で0
になり、最初のスライド(0番)に戻る、というスムーズなループを実現しています。前の方向に戻る際も、もしcount
が負の数になっても、この計算で正しいインデックスに戻ります。- 新しく表示するスライドとインジケーターに
active
クラスを付けます。(これで新しいスライドが表示されます) - 最後に
setTimeout
の部分(修正版で追加!)で、500ミリ秒(0.5秒)後にisTransitioning
フラグをfalse
に戻しています。この「500ミリ秒」は、CSSで設定しているスライドの切り替えアニメーションにかかる時間に合わせて調整してください。アニメーションが終わった頃にフラグを戻すことで、次の操作を受け付けられるようにします。
「次へ」「前へ」「好きなスライドへ」の操作関数
function nextSlide() {
updateSlides(currentSlide + 1); // 今のスライド番号に1を足して、updateSlidesを呼ぶ
}
function prevSlide() {
updateSlides(currentSlide - 1); // 今のスライド番号から1を引いて、updateSlidesを呼ぶ
}
function goToSlide(index) {
if (index === currentSlide) return; // もし押したインジケーターが今のスライドと同じなら何もしない
updateSlides(index); // 指定された番号のインデックスでupdateSlidesを呼ぶ
}
function nextSlide() {
updateSlides(currentSlide + 1); // 今のスライド番号に1を足して、updateSlidesを呼ぶ
}
function prevSlide() {
updateSlides(currentSlide – 1); // 今のスライド番号から1を引いて、updateSlidesを呼ぶ
}
function goToSlide(index) {
if (index === currentSlide) return; // もし押したインジケーターが今のスライドと同じなら何もしない
updateSlides(index); // 指定された番号のインデックスでupdateSlidesを呼ぶ
}
function startAutoPlay() {
autoPlayInterval = setInterval(nextSlide, 5000); // 5000ミリ秒(5秒)ごとにnextSlide関数を実行するタイマーを開始
}
function resetAutoPlayInterval() {
clearInterval(autoPlayInterval); // 今動いているタイマーを一旦止める
startAutoPlay(); // 新しくタイマーを開始する
}
startAutoPlay
関数は、JavaScriptの setInterval
という機能を使って、「指定した時間(例:5000ミリ秒 = 5秒)ごとに、指定した関数(例:nextSlide
)を繰り返し実行する」というタイマーを設定します。設定したタイマーは autoPlayInterval
という変数に記録しておきます。
resetAutoPlayInterval
関数は、一度動いているタイマーを clearInterval
で止め、その後すぐに startAutoPlay
を呼び出して新しいタイマーを再開します。これは、手動でボタンを押したりインジケーターをクリックしたりした際に、自動再生のタイマーを一度リセットして、操作後から再び5秒を数え直すために使われます。こうしないと、手動で操作した直後に意図せず自動で次のスライドに進んでしまう、といったことがなくなります。
ボタンやインジケーターのクリックに反応する
// Event Listeners
next.addEventListener('click', () => { // 「次へ」ボタンがクリックされたら
nextSlide(); // 次のスライドを表示して
resetAutoPlayInterval(); // 自動再生タイマーをリセットする
});
prev.addEventListener('click', () => { // 「前へ」ボタンがクリックされたら
prevSlide(); // 前のスライドを表示して
resetAutoPlayInterval(); // 自動再生タイマーをリセットする
});
indicator.addEventListener('click', (event) => { // インジケーター全体がクリックされたら
if (event.target.classList.contains('list')) { // もしクリックされたのが、インジケーターの中の個々の要素(.listクラスがついたもの)なら
const index = Array.from(lists).indexOf(event.target); // クリックされた要素が何番目かを調べる
goToSlide(index); // その番号のスライドに移動して
resetAutoPlayInterval(); // 自動再生タイマーをリセットする
}
});
ここでは、HTMLで取得したボタンやインジケーターに対して、クリックされたときにJavaScriptの関数を実行するように設定しています。これをイベントリスナーと呼びます。
next.addEventListener('click', ...)
:next
ボタンがクリックされたら、先ほど作ったnextSlide
関数を実行し、その後resetAutoPlayInterval
でタイマーをリセットします。prev.addEventListener('click', ...)
:prev
ボタンがクリックされたら、prevSlide
関数を実行し、タイマーをリセットします。indicator.addEventListener('click', ...)
:インジケーター全体をクリックしたときにイベントを受け取ります。if (event.target.classList.contains('list'))
で、「クリックされたのが、インジケーターの点(.list
クラスが付いた要素)だった場合だけ」という条件を付けています。クリックされたのが.list
要素だったら、それがlists
の中の何番目か (index
) を調べて、その番号をgoToSlide
関数に渡して実行し、タイマーをリセットします。
自動再生を開始する
// Start autoplay
startAutoPlay(); // ページが読み込まれたら自動再生を開始する
最後に、startAutoPlay()
を呼び出すことで、ページが読み込まれた後、このスクリプトの実行が完了した時点で、自動再生がスタートします。
このJavaScriptコードを使うための準備
このJavaScriptコードは、単独では動きません。以下のHTMLとCSSが適切に準備されている必要があります。
- HTML:
id="tslide"
の要素(スライド全体を囲む)id="prev"
の要素(「前へ」ボタン)id="next"
の要素(「次へ」ボタン)id="indicator"
の要素(インジケーター全体を囲む)id="tslide"
の中に、表示したいスライド一つ一つを<div>
などでマークアップ。(このコードでは、これらのdiv
がslides
変数に入ります)id="indicator"
の中に、インジケーターの点や項目を.list
というクラスを付けてマークアップ。(このコードでは、これらの要素がlists
変数に入ります)
- CSS:
- スライド全体 (
#tslide
の中のdiv
要素など) に対して、普段は非表示にしておき、.active
クラスが付いたときだけ表示されるようにするスタイル。opacity
やtransform
などを使ったトランジション(アニメーション)を設定しておけば、ふわっと切り替わるような表現が可能です。 - インジケーター (
.list
要素) に対して、.active
クラスが付いたときに見た目が変わる(例: 色が変わる、大きくなるなど)スタイル。
- スライド全体 (
これらの準備ができていれば、今回のJavaScriptコードを適用することで、スムーズに動き、操作もできるスライドショーが完成します!
この修正版コードを使えば、前回よりももっと自然な動きのスライドショーが作れるはずです。ぜひあなたのブログで試してみてくださいね!