はじめに
前回、フィルターについてcodepen challengeを行った記事を書いたのですが、今回は新しくスクロールについてcodepen challengeに調整んしてみたので、そちらを記事にしていきます。
詳しくは前回の記事を参照してください。被写界深度再現してみた
今回作ったもの
今回作ったものは自分のお気に入りの絵本をスクロールアニメーションで紹介していくものです。
0.25倍でみて もらえるとちゃんと見えるかと思います。。。(レスポンシブ対応できてない。。。)
See the Pen Scroll story book by Hayuse (@dtweqbgd-the-vuer) on CodePen.
今日はこのプロジェクトをどのように作ったのかを説明していけたらなと思います。(画像も含めて)
ロードマップはこんな感じ。
この記事ではコーディングの箇所について詳しく解説していきます。
スクロールによって特定の要素が映った時点で、実行させるってよくやりますよね。
今回は、jsのAPIのIntersectionObserverで簡単にそれが実装できたのでそれを紹介してます。
IntersectionObserverとは?
今回scrollの発火に使ったのはこのIntersectionObeserverです。
こいつなんと、画面内に特定のエレメントがあるかどうかを監視してくれるAPIなんです。
具体的な使い方は
const scrollIntersect = () => {
console.log("画面に映った!!");
}
// IntersectionObserverの設定
const options = {
root: null, // root nullで表示画面全体に交錯したか(映ったか)を設定。(elementでもできるぽい)
rootMargin: '0px', // elementの判定の大きさ(大きくしたら映った判定がガバガバになる)
threshold: 0.1 // 何%映ったら、発火させるか。0.1 = 10%
};
// IntersectionObserverを設定(上記のoptionの設定でscrollIntersect関数を発火させる)
const scrollObserver = new IntersectionObserver(scrollIntersect, options);
// triggerクラスを発火するトリガーとしてobserver開始の開始。
const pageTriggers = document.querySelectorAll('.trigger');
pageTriggers.forEach(trigger => scrollObserver.observe(trigger));
こんな感じ。
コードの説明としては、trigger
のクラス名のついたオブジェクトが、画面に映った場合、scrollIntersect
関数を発火させています。
実装手順
概要
今回特に行いたかった実装は、
1.特定のエレメントが見えたら、ページのスクロールを自動調整
2.自動調整が終わったらアニメーションの開始
この二つを実装するために、今回はスクロールを自動調整するobserverとアニメーションを実行するobserverの2つを使って実装しています。
イメージとしてはこんな感じ
手順1:枠組み作成と発火させるエレメントの配置
用意した画像を調整して配置するのと、発火のトリガーとなる要素を自分の好きなところに表示していきます。
See the Pen Untitled by Hayuse (@dtweqbgd-the-vuer) on CodePen.
赤いのがスクロール調整関数を発火させる用のエレメント
黄色いのがアニメーション関数を発火させる用のエレメント
手順2:jsでスクロール用のobserverの配置
ここで出てきますIntersectionObeserver
赤いエレメントをトリガーにしてobserverを開始していきます。
See the Pen Untitled by Hayuse (@dtweqbgd-the-vuer) on CodePen.
この関数で特定のクラスをquerySelectorAll
したNodeListを渡します。
そうすると要素が映った瞬間にscrollFunctionの関数が発火します。
const observeStart = (triggerElements: NodeListOf<HTMLElement>) => {
// IntersectionObserverの設定
const options = {
root: null, // root nullで表示画面全体に交錯したか(映ったか)を設定。(elementでもできるぽい)
rootMargin: '0px', // elementの判定の大きさ(大きくしたら映った判定がガバガバになる)
threshold: 0.1 // 何%映ったら、発火させるか。0.1 = 10%
};
// IntersectionObserverを設定(上記のoptionの設定でscrollFunction関数を発火させる)
const scrollObserver = new IntersectionObserver(scrollFunction, options);
// triggerクラスを発火するトリガーとしてobserver開始の開始。
for (const trigger of triggerElements) {
scrollObserver.observe(trigger)
}
}
scrollFunction関数
const scrollFunction = (entries: IntersectionObserverEntry[], _observer: IntersectionObserver) => {
for (const entry of entries) {
const triggerElement = entry.target;
if (entry.isIntersecting) { // トリガーとなる要素が画面に映ったらここがtrueになる。
triggerElement.innerHTML = "<span>画面に映った!!→</span>";
console.log("画面に映った!!");
} else {
triggerElement.innerHTML = "<span>画面に映ってない!!→</span>";
console.log("画面に映ってない!!");
}
}
}
関数の引数に自動的に要素のデータやその他諸々のデータが入ってくるので、実行したい条件をそれらから使うといいかもしれません。
ちなみに映ってるか映ってないかをtrue,falseで返してくれるのはisIntersectingみたいです。
手順3:自動でスクロール量を調整
要素が映ったタイミングで自動で1ページ分スクロールするようにscrollFunctionに手を加えます。
See the Pen observer_step2 by Hayuse (@dtweqbgd-the-vuer) on CodePen.
const scrollFunction = (entries: IntersectionObserverEntry[], _observer: IntersectionObserver) => {
for (const entry of entries) {
const triggerElement = entry.target;
if (entry.isIntersecting) { // トリガーとなる要素が画面に映ったらここがtrueになる。
triggerElement.innerHTML = "<span>画面に映った!!→</span>";
console.log("画面に映った!!");
// 1ページの要素を取得
const pageElm = triggerElement.closest<HTMLElement>(".frame-page");
if (pageElm === null) return;
// 要素が左から何pxのとこにあるのか取得
const pageX = pageElm.offsetLeft;
console.log(pageElm.getBoundingClientRect());
// スクロールさせる
window.scrollTo({
top: window.scrollY,
left: pageX,
behavior: 'smooth'
});
} else {
triggerElement.innerHTML = "<span>画面に映ってない!!→</span>";
console.log("画面に映ってない!!");
}
}
}
手順4:アニメーションの発火
スクロールの発火と同じようにして今度は、黄色のトリガーが映ったタイミングでアニメーションを発火させます。
See the Pen observer_step3 by Hayuse (@dtweqbgd-the-vuer) on CodePen.
const animtionFunction = (entries: IntersectionObserverEntry[], _observer: IntersectionObserver) => {
for (const entry of entries) {
const triggerElement = entry.target;
if (entry.isIntersecting) { // トリガーとなる要素が画面に映ったらここがtrueになる。
console.log("アニメーションスタート");
const currentPage = triggerElement.closest('.frame-page');
if (currentPage === null) return;
currentPage.classList.add('is-active')
} else {
console.log("アニメーションストップ");
const currentPage = triggerElement.closest('.frame-page');
if (currentPage === null) return;
currentPage.classList.remove('is-active')
}
}
}
ここはほぼ同じロジックですね。前回のコードを少し変えて、observerの関数にトリガーと発火させたい関数を渡すようにしています。
type observeFunction = (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => void;
const observeStart = (triggerElements: NodeListOf<HTMLElement>, triggerFunction: observeFunction) => { // triggerFunctionに発火させたい関数を渡す。
// IntersectionObserverの設定
const options = {
root: null, // root nullで表示画面全体に交錯したか(映ったか)を設定。(elementでもできるぽい)
rootMargin: '0px', // elementの判定の大きさ(大きくしたら映った判定がガバガバになる)
threshold: 0.1 // 何%映ったら、発火させるか。0.1 = 10%
};
// IntersectionObserverを設定(上記のoptionの設定でscrollIntersect関数を発火させる)
const scrollObserver = new IntersectionObserver(triggerFunction, options);
// triggerクラスを発火するトリガーとしてobserver開始の開始。
for (const trigger of triggerElements) {
scrollObserver.observe(trigger)
}
}
あとはロードのタイミングで発火させるトリガーのNodeListと関数を渡してあげればスッキリコードの完成!
document.addEventListener("DOMContentLoaded", () => {
const scrollTrigger = document.querySelectorAll<HTMLElement>(".scroll-trigger");
observeStart(scrollTrigger, scrollFunction);
const animationTrigger = document.querySelectorAll<HTMLElement>(".animation-trigger");
observeStart(animationTrigger, animtionFunction);
});
完成に向けて
あとはHTMLに切りはりした画像を別々に乗っけたり,cssを動くように書き直せば動くはずです。
今回使用した画像は全部AIに生成してもらったので別記事でAI生成のさせ方と、コツなどをまとめていけたらなと思います。