この記事では
通常では、「Slick」や「Swiper」「FullPage.js」といったjavascriptのライブラリを使って実装することがほとんどだと思いますが、今回は素のjavascriptで実装する方法を解説します。
※実際は画面いっぱいのサイズになります。(周りもスライドするので操作しにくいと思います、、あくまでイメージです)
今回は、ご覧いただいたような、縦に一枚ごとにスクロールする画面いっぱいのスライド(切り替えボタン付き)の作り方を解説します。ちろんコピペもOKです!
色々な作り方があると思いますが、一例としてご覧ください。
こんな人にオススメ!
- 素のjavascriptで画面いっぱいの縦スライドを作ってみたい
- スライドごとにスクロールを止めたい
- 画面端にインジケーターも付けてみたい
全画面表示の縦スライドの完成コード
まずは、縦スライド機能を実装した全画面ページの完成コード(例:スライド3枚)をご覧ください。
javascript <div class="slide">スライド 1</div> <!-- 1つ目の画面 --> <div class="slide">スライド 2</div> <!-- 2つ目の画面 --> <div class="slide">スライド 3</div> <!-- 3つ目の画面 --> <div class="indicator"></div> <!-- インジケーター -->
html, body { margin: 0; padding: 0; overflow: hidden; /* スクロールバーを非表示 */ } .slide { height: 100vh; display: flex; align-items: center; justify-content: center; font-size: 2rem; color: #333; } .slide:nth-of-type(1) { background-color: #ffcccc; } .slide:nth-of-type(2) { background-color: #ccffcc; } .slide:nth-of-type(3) { background-color: #ccccff; } .indicator { position: fixed; right: 10px; top: 50%; transform: translateY(-50%); border-radius: 10px; padding: 10px 20px; display: flex; flex-direction: column; align-items: center; } .indicator div { width: 10px; height: 10px; border-radius: 50%; margin: 6px 0; background-color: #fff; /* デフォルトの背景色 */ cursor: pointer; border: 2px solid #333; }
document.addEventListener("DOMContentLoaded", () => { const slides = document.querySelectorAll(".slide"); const indicator = document.querySelector(".indicator"); let currentSlideIndex = 0; let isScrolling = false; let touchStartY = 0; let touchEndY = 0; // インジケーターの初期化 slides.forEach((_, index) => { const dot = document.createElement("div"); dot.addEventListener("click", () => { currentSlideIndex = index; scrollToSlide(currentSlideIndex); updateIndicator(); }); indicator.appendChild(dot); }); // スムーズスクロール const scrollToSlide = (index) => { slides[index].scrollIntoView({ behavior: "smooth" }); isScrolling = true; setTimeout(() => { isScrolling = false; }, 1000); // 時間を調整 }; // インジケーターのスタイル更新 const updateIndicator = () => { Array.from(indicator.children).forEach((dot, index) => { dot.style.backgroundColor = index === currentSlideIndex ? "#333" : "#fff"; }); }; // ホイールイベントでスライドを切り替え window.addEventListener("wheel", (event) => { if (isScrolling) return; if (event.deltaY > 0 && currentSlideIndex < slides.length - 1) { currentSlideIndex++; } else if (event.deltaY < 0 && currentSlideIndex > 0) { currentSlideIndex--; } scrollToSlide(currentSlideIndex); updateIndicator(); }); // タッチイベントでスライドを切り替え window.addEventListener("touchstart", (event) => { touchStartY = event.touches[0].clientY; }); window.addEventListener("touchmove", (event) => { event.preventDefault(); }, { passive: false }); window.addEventListener("touchend", (event) => { touchEndY = event.changedTouches[0].clientY; handleSwipe(); }); // スワイプ方向を判定 const handleSwipe = () => { if (isScrolling) return; const swipeThreshold = 50; if (touchStartY - touchEndY > swipeThreshold && currentSlideIndex < slides.length - 1) { currentSlideIndex++; } else if (touchEndY - touchStartY > swipeThreshold && currentSlideIndex > 0) { currentSlideIndex--; } scrollToSlide(currentSlideIndex); updateIndicator(); }; updateIndicator(); });
完成コードを図解にすると、上記のようになります。
スライド(slide)の縦スクロールと、画面右中央の切り替えボタン=インジケーター( indicator )の機能を実装しています。
HTML・CSSの解説
まずは、今回作成する全画面ページの縦スライドの仕組みを「HTML」と「CSS」を中心に簡単に説明します。
各部位ごとに、コードを抜粋して重要なポイントを解説しているのでご覧ください。
スライド(例:スライド3枚の場合)
<div class="slide">スライド 1</div> <!-- 1つ目の画面 -->
<div class="slide">スライド 2</div> <!-- 2つ目の画面 -->
<div class="slide">スライド 3</div> <!-- 3つ目の画面 -->
html, body {
margin: 0;
padding: 0;
overflow: hidden; /* スクロールバーを非表示 */
}
.slide {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
color: #333;
}
.slide:nth-of-type(1) {
background-color: #ffcccc;
}
.slide:nth-of-type(2) {
background-color: #ccffcc;
}
.slide:nth-of-type(3) {
background-color: #ccccff;
}
slide のポイント
width: 100%;
になるので省略しています。)また、余計なスライドを発生させないために、CSSでhtml,bodyに対してoverflow: hidden;
を設定します。
インジケーター(切り替えボタン)
<div class="indicator"></div> <!-- インジケーター -->
.indicator {
position: fixed;
right: 10px;
top: 50%;
transform: translateY(-50%);
border-radius: 10px;
padding: 10px 20px;
display: flex;
flex-direction: column;
align-items: center;
}
.indicator div {
width: 10px;
height: 10px;
border-radius: 50%;
margin: 6px 0;
background-color: #fff; /* デフォルトの背景色 */
cursor: pointer;
border: 2px solid #333;
}
indicator のポイント
また、CSSでは複数のインジケーターがある場合を考慮して、マージンなどを設定しておきます。
Javascriptの解説
ここからは、各部位に関係する「javascript」について詳しく解説していきます。
まずは、PC画面でスライドを実装させるためのjavascriptについて解説します。
スライドの実装
PC用の「全画面ページ」を一枚ずつスライドする仕組みを作ります。
仕組みを簡単に説明すると、「ホイールイベント」でマウスのホイールを1pxでも上、または下に動かすのをきっかけにして、scrollIntoView
メソッド を使って対象の要素(スライド)を画面の見える範囲に移動させることでスライドを実現しています。
「スクロールイベント」ではなく「ホイールイベント」を使用する理由は、
スライドのHTML /CSSで解説したとおり、CSSでhtml,bodyに対して「overflow: hidden;
」で、普通のスクロールを無効にしているためです。
全画面の縦スライドに関係するjavascriptのコードは、以下のとおりです。
document.addEventListener("DOMContentLoaded", () => {
const slides = document.querySelectorAll(".slide");
let currentSlideIndex = 0;
let isScrolling = false;
// セクションへのスムーズスクロール
const scrollToSlide = (index) => {
slides[index].scrollIntoView({ behavior: "smooth" });
isScrolling = true;
setTimeout(() => {
isScrolling = false;
}, 1000);
};
// ホイールイベントでセクションを切り替え
window.addEventListener("wheel", (event) => {
if (isScrolling) return;
if (event.deltaY > 0 && currentSlideIndex < slides.length - 1) {
currentSlideIndex++;
} else if (event.deltaY < 0 && currentSlideIndex > 0) {
currentSlideIndex--;
}
scrollToSlide(currentSlideIndex);
});
});
それでは、ステップごとに詳しくみていきましょう。
step
1定数と変数を定義する
まずは、スライドを操作するために、関係する各要素を取得しておきます。
const slides = document.querySelectorAll(".slide");
let currentSlideIndex = 0;
let isScrolling = false;
document.querySelectorAllで、全てのスライド(.slide)の要素を取得し、
currentSlideIndex で、現在表示しているスライドのインデックス(順番)を判定します。
これは、ホイールイベントやインジケーターのクリックに応じてインデックスを更新し、次に表示すべきスライドの判定に使います。
isScrolling では、スクロール中であるかどうかの状態を判定しています。
これは、スクロールイベントが連続で発生するのを防ぐことで、スクロール完了まで次のスクロール操作を無効にしています。
step
21枚ずつ切り替える機能の実装
次に、先ほど取得した「slide」と作成した変数「isScrolling」を使って1枚ずつスライドさせる仕組みを作ります。
// セクションへのスムーズスクロール
const scrollToSlide = (index) => {
slides[index].scrollIntoView({ behavior: "smooth" });
isScrolling = true;
setTimeout(() => {
isScrolling = false;
}, 1000);
};
scrollToSlide 関数は、引数として受け取ったスライドのインデックス(index
)を使って、そのスライドにスムーズにスクロールする機能を持っています。
scrollIntoView
は、要素を画面内にスクロールするためのメソッドで、オプションとしてbehavior: "smooth"
を設定することでスムーズなスクロールを実現しています。
よって、slides[index].scrollIntoView({ behavior: "smooth" });
とすることで、指定された index
のスライド要素を画面内にスムーズにスクロールさせることができます。
isScrolling は、scrollToSlide 関数が実行中(スクロール中)に「true」にし、setTimeout
関数を使って1秒後に「false」とすることで、スクロール発生後、1秒間は新たなスクロールが発生しないようにしています。
step
3ホイールイベントの実装
最後に、先ほど作成した任意のスライドにスムーズにスクロールする scrollToSlide関数を、ホイールイベント(wheel)と紐付けます。
window.addEventListener("wheel", (event) => {
if (isScrolling) return;
if (event.deltaY > 0 && currentSlideIndex < slides.length - 1) {
currentSlideIndex++;
} else if (event.deltaY < 0 && currentSlideIndex > 0) {
currentSlideIndex--;
}
scrollToSlide(currentSlideIndex);
});
window.addEventListener("wheel", ...)
の部分では、ホイール操作が行われたときに発火するリスナーを設定しています。
(event) => { ... }
という部分がイベントが発生したときに実行される関数(無名関数)になります。
if (isScrolling) return; の部分では、スライドがスクロール中かを判定しています。
先ほど、scrollToSlide 関数の中に記述して「true」にしたのち、settimeout関数で時間経過後に「false」にしたのは、連続したスライドを無効にするためです。
つまり、スライド中であれば処理を中断することで、一枚ずつスライドする動きを実装しているわけです。
以下のコードでは、マウスホイールの上下の動きを判定し、スライド向きを制御しています。
if (event.deltaY > 0 && currentSlideIndex < slides.length - 1) {
currentSlideIndex++;
} else if (event.deltaY < 0 && currentSlideIndex > 0) {
currentSlideIndex--;
}
event.deltaY
は、スクロールやホイールイベントにおいて、マウスホイールやタッチパッドの移動量を示すプロパティです。
この値は、どれだけスクロールされるかを「上下方向のピクセル量」で表していて、スクロールの方向と強さを判定しています。
- 正の値 (
> 0
):下方向 = true(通常は下スクロール) - 負の値 (
< 0
):上方向 = false(通常は上スクロール)
currentSlideIndex
の部分では、数値を変更させることでスライドの順番を表しているよ!ちなみに、
currentSlideIndex
=0 の時はスライド1枚目
currentSlideIndex
=1 の時はスライド2枚目
currentSlideIndex
=2 の時はスライド3枚目 だよ!
よって、slides.length - 1
は、最後のスライドを意味します。(例:スライドが3枚の場合は「2」になる)
(event.deltaY > 0 && currentSlideIndex < slides.length - 1)
の部分では、
event.deltaY > 0
で、マウスホイールが「下方向」に動かされたときにdeltaY
が正の値を返すため、この条件がtrue
になる。currentSlideIndex < slides.length - 1
では、現在のスライドインデックスが、スライドの最後のインデックス(slides.length - 1
)未満であることを確認する条件です。
つまり、「マウスが 下 に動かされた時、且つ最後のページ未満の場合」という条件が満たされるとことで、
currentSlideIndex++
によってスライドインデックスが1つ進みます。
event.deltaY < 0 && currentSlideIndex > 0
の部分は、
event.deltaY < 0
では、マウスホイールが「上方向」に動かされたときにdeltaY
が負の値を返すため、この条件がtrue
になります。currentSlideIndex > 0
では、現在のスライドインデックスが0より大きい場合を表しています。
つまり、「マウスが 上 に動かされた時、且つ現在のスライドインデックスが0より大きい場合」という条件が満たされるとことで、
currentSlideIndex--
によってスライドインデックスが1つ戻ります。
インジケーターの実装
スライドすると対象のスライドの色が変わり、クリックするとスライドする「インジケーター」の仕組みを作ります。
以下のコードがインジケーターに関係するコードです。
// インジケーターの初期化
slides.forEach((_, index) => {
const dot = document.createElement("div");
dot.addEventListener("click", () => {
currentSlideIndex = index;
scrollToSlide(currentSlideIndex);
updateIndicator();
});
indicator.appendChild(dot);
});
// インジケーターのスタイル更新
const updateIndicator = () => {
Array.from(indicator.children).forEach((dot, index) => {
dot.style.backgroundColor = index === currentSlideIndex ? "#333" : "#fff";
});
};
updateIndicator(); // 初期表示のインジケーターを設定
});
それでは、ステップごとに詳しくみていきましょう。
step
1インジケーターをスライドの数だけ生成する
まずは、スライドの数だけインジケーターを生成します。
slides.forEach((_, index) => {
const dot = document.createElement("div");
dot.addEventListener("click", () => {
currentSlideIndex = index;
scrollToSlide(currentSlideIndex);
});
indicator.appendChild(dot);
});
slides.forEach((_, index) => {...})
の部分で、すべてのスライド(slides)に対してループを回して処理を実行しています。
forEach メソッドは配列の各要素を一度ずつ処理するためのメソッドで、以下のように使われます。
array.forEach((element, index) => {
// ここで element と index を使って処理
});
- element:現在処理中の配列の要素
- index:現在処理中の要素のインデックス番号(0 から始まる)
そのため、(_, index) という構文では、第一引数(_) は 使用しない ことを意味し、第二引数(index) はその要素の インデックス番号 を指しています。
document.querySelectorAll
で取得した要素リスト「配列風のオブジェクト」(NodeList)にも使えるよ!
そして、
const dot = document.createElement("div");
の部分で、div要素を新たに生成し、
indicator.appendChild(dot);
で、作成したドット(dot)要素をindicatorコンテナに追加します。
こうすることで、スライド(slides)の数だけインジケーター(dot) を生成しています。
ちなみに、HTMLは以下のように生成されます。
<div class="indicator">
<div></div> <!-- 最初のドット -->
<div></div> <!-- 2つ目のドット -->
<div></div> <!-- 3つ目のドット -->
<!-- 他のドットも同様に追加 -->
</div>
step
2インジケーターをクリック可能にする
次に、生成したインジケーターをクリック可能にします。
slides.forEach((_, index) => {
const dot = document.createElement("div");
dot.addEventListener("click", () => {
currentSlideIndex = index;
scrollToSlide(currentSlideIndex);
});
indicator.appendChild(dot);
});
dot.addEventListener("click", () => {...})
の部分で、各ドット(dot)にクリックイベントを追加し、
関数内のcurrentSlideIndex = index;
の部分で、クリックしたドットの順番(index)を currentSlideIndex
に代入し、現在のスライド位置を更新します。
そして、
scrollToSlide(currentSlideIndex);
で、クリックされたドットの番号にスライドさせています。
scrollToSlide
関数は、スライドの実装のstep2で作成した関数だね!
step
3スライドに対応したインジケーターの色を変える
さらに、クリックされたインジケーターの色を変更します。(今回は黒 #333に変更)
const updateIndicator = () => {
Array.from(indicator.children).forEach((dot, index) => {
dot.style.backgroundColor = index === currentSlideIndex ? "#333" : "#fff";
});
};
updateIndicator();
});
Array.from(indicator.children).forEach((dot, index) => { ... });
の部分で、全てのインジケーターにループ処理を行います。
詳しく説明すると、
indicator.children
でインジケーターのコンテナ内のすべてのドット(各スライドに対応)を取得します。Array.from
で配列に変換します。- forEach((dot, index) => {...}) では、
forEach
で 各ドットに対してループを回して処理 しています。
【第一引数】dot
:indicator.children
から取得した現在のインジケーター要素を指します。
【第二引数】index
:dot
がindicator.children
の中で何番目に位置するかを示すインデックス番号です。
Array.from
で indicator.children
を配列に変換する理由は、HTMLCollection と呼ばれるオブジェクトで、forEach
が使うためだよ!
dot.style.backgroundColor = index === currentSlideIndex ? "#333" : "#fff";
の部分では、ドットに対して選択中のインジケーターの色を黒(#333)、それ以外を白(#fff)にしています。
(これは、比較演算子と、三項演算子を組み合わせた文です。)
詳しく説明すると、
index === currentSlideIndex
の部分で、index (インジケーターのドット番号)と currentSlideIndex(表示中のスライドのインデックス)と比較しています。"#333" : "#fff";
の部分で、①の比較結果が同じであれば#333
(暗色)を、違っていれば#fff
(白)を返します。- ②で返ってきた結果を
dot.style.backgroundColor
に、代入します。
これにより、表示中のスライドのインデックスと同じインジケーターの番号のドットを黒く、それ以外を白くしています。
step
4関数にまとめて呼び出す
最後に、これまでの「インジケーター」に関する処理を関数にまとめて、呼び出します。
const updateIndicator = () => {
Array.from(indicator.children).forEach((dot, index) => {
dot.style.backgroundColor = index === currentSlideIndex ? "#333" : "#fff";
});
};
updateIndicator();
});
インジケーターの一連の動作を関数(const updateIndicator = () => {}
)にまとめて、呼び出すupdateIndicator();
ことでインジケータの初期化を実行します。
また、インジケーターをクリックした際にも、インジケーターの背景色を初期化したいので、step2で作成した関数の内部にも追記します。
slides.forEach((_, index) => {
const dot = document.createElement("div");
dot.addEventListener("click", () => {
currentSlideIndex = index;
scrollToSlide(currentSlideIndex);
updateIndicator();
});
indicator.appendChild(dot);
});
カスタマイズ
今回作成したスライドショーを実際に使用する場合、スライドの数を増やしたり、スライドを写真に変更したりと色々と手を加えたいですよね?
ここからは、状況に応じた色々なカスタマイズ方法を詳しく解説します。
スマホやタブレットでも動かす
スマホやタブレットにも今回の全画面の縦スライドの動きを実装するためには、さらに次のjavascriptを追記する必要があります。
// タッチイベントでスライドを切り替え
window.addEventListener("touchstart", (event) => {
touchStartY = event.touches[0].clientY;
});
window.addEventListener("touchmove", (event) => {
event.preventDefault();
}, { passive: false });
window.addEventListener("touchend", (event) => {
touchEndY = event.changedTouches[0].clientY;
handleSwipe();
});
// スワイプ方向を判定
const handleSwipe = () => {
if (isScrolling) return;
const swipeThreshold = 50;
if (touchStartY - touchEndY > swipeThreshold && currentSlideIndex < slides.length - 1) {
currentSlideIndex++;
} else if (touchEndY - touchStartY > swipeThreshold && currentSlideIndex > 0) {
currentSlideIndex--;
}
scrollToSlide(currentSlideIndex);
updateIndicator();
};
});
それでは詳しくみていきましょう。
step
1タッチイベントでスライドを切り替える
まずは、画面を指でスワイプする方向を読み取ります。
ここでは、touchstart、touchmove、 touchend の組み合わせで、画面上の指の動きを検知しています。
window.addEventListener("touchstart", (event) => {
touchStartY = event.touches[0].clientY;
});
window.addEventListener("touchmove", (event) => {
event.preventDefault();
}, { passive: false });
window.addEventListener("touchend", (event) => {
touchEndY = event.changedTouches[0].clientY;
});
window.addEventListener("touchstart", (event) => {...});
の部分は、指が画面に触れたときに発生するイベントを設定しています。
touchStartY = event.touches[0].clientY;
の部分では、event.touches[0].clientY
で、最初の指が画面に触れた位置のY座標(縦方向のピクセル位置)を取得します。
そして、取得した値を変数(touchStartY
)に格納しています。
event.touches
は、指の位置の情報を含む配列で、マルチタッチの場合に複数のタッチポイントを管理できるよ!touches[0]
は最初の指という意味だよ!
window.addEventListener("touchmove", (event) => {...});
の部分では、指が画面上を動いている間に発生するイベントを設定しています。
event.preventDefault();
の部分で、ブラウザのデフォルトのスクロール動作を無効にし、縦スクロールやページ全体のスクロールが防止され、意図しない動きが生じないようにしています。
加えて、{ passive: false }
オプションで、event.preventDefault()
が有効になるように設定しています。
{ passive: true }
とすることが多いけど、ここでは false
にしてスクロールを制御しやすくしてるよ!
window.addEventListener("touchend", (event) => {...});
の部分では、画面から指を離したときに発生するイベントを設定しています。
touchEndY = event.changedTouches[0].clientY;
で、指が画面から離れたときの Y座標(垂直位置)を取得し、変数 touchEndY に格納しています。
touchStartY
と touchEndY
)を使ってスライドさせるよ!
step
2スワイプ方向を判定する
「ステップ1」で検知したユーザーのスワイプの方向を元に、実際にスライドを行います。
const handleSwipe = () => {
if (isScrolling) return;
const swipeThreshold = 50;
if (touchStartY - touchEndY > swipeThreshold && currentSlideIndex < slides.length - 1) {
currentSlideIndex++;
} else if (touchEndY - touchStartY > swipeThreshold && currentSlideIndex > 0) {
currentSlideIndex--;
}
scrollToSlide(currentSlideIndex);
updateIndicator();
};
});
if (isScrolling) return;
で、現在スクロール中であれば処理を中断します。
isScrolling
は、スライドの実装で定義した変数だよ!(詳しくはコチラ)const swipeThreshold = 50; では、スワイプの動きが成立するための閾値を設定しています。
if (touchStartY - touchEndY > swipeThreshold && currentSlideIndex < slides.length - 1)
の部分では、
touchStartY - touchEndY > swipeThreshold
で、指が「下↓」から「上↑」へ移動した距離がswipeThreshold
( 50ピクセル)より大きい場合、上方向へのスワイプと判断しています。currentSlideIndex < slides.length - 1
では、現在のスライドインデックスが、スライドの最後のインデックス(slides.length - 1
)未満であることを確認しています。
つまり、「指が下から上へ動いたときの距離が閾値を超え、かつ現在のスライドが最終スライドでない場合」という条件が満たされることで、
currentSlideIndex++
によってスライドが1つ進みます。
else if (touchEndY - touchStartY > swipeThreshold && currentSlideIndex > 0)
の部分では、
touchEndY - touchStartY > swipeThreshold
で、指が「上↑」から「下↓」へ移動した距離がswipeThreshold
( 50ピクセル)より大きい場合、下方向へのスワイプと判断されます。currentSlideIndex > 0
では、現在のスライドインデックスが、最初のインデックスでないことを確認しています。
つまり、「指が上から下へ動いたときの距離が閾値を超え、かつ現在のスライドが最初のスライドでない場合」という条件が満たすことで、
currentSlideIndex--
によってスライドが1つ戻ります。
最後に、
scrollToSlide(currentSlideIndex);
で、対応するスライド位置にスクロールを移動し、
updateIndicator();
で、スライドインジケーターを更新します。
スライドを増やす
スライドを増やす場合は、HTMLのみの変更で対応可能です。
例としてスライドを1枚追加する場合(スライドが4枚の場合)を見てみましょう。
スライドを1枚増やすために、スライド(.slide)の<div>...</div>を追加します。
<div class="slide">スライド 1</div>
<div class="slide">スライド 2</div>
<div class="slide">スライド 3</div>
<div class="slide">スライド 4</div> <!-- 追加するスライド -->
<div class="indicator"></div> <!-- インジケーター -->
インジケーターのドットの数は、スライドの数に応じて生成されるので設定は不要です。
最後に
今回は、素のJavaScriptを使って全画面の縦スライドの機能を実装しました。
ぜひ自分のウェブサイトやプロジェクトに取り入れて、魅力的なコンテンツを演出してみてくださね!
他のjavascriptのパーツについても知りたくなった方は、こちらも覧ください!
JSパーツコレクション