要素を監視して画面に入ったらふわっと表示させるスクロールアニメーション
JavaScriptのIntersectionObserverを利用し、画面内に要素が入ったらふわっと表示させるスクロール連動アニメーションを作成したいと思います。
IntersectionObserverで要素を監視する
IntersectionObserverを使って、アニメーションさせる対象の要素を監視します。
今回はjs-in-view
というクラスの要素が画面内に入ってきたら、属性(もしくはクラス)を与えるといった処理を実装していきます。
基本形
基本的にはこれでOK。
// IntersectionObserver を作成
const observer = new IntersectionObserver((entries) => {
entries.forEach(function (entry) {
// 要素がビューポートに入った場合
if (entry.isIntersecting) {
// 表示属性を付与
entry.target.setAttribute("data-visible", "true");
// 一度監視したら、対象要素の監視を停止(必要に応じてコメントアウト)
observer.unobserve(entry.target);
} else {
// ビューポートから外れたら非表示属性を付与
entry.target.setAttribute("data-visible", "false");
}
});
});
// 監視要素
const viewElements = document.querySelectorAll(".js-in-view");
viewElements.forEach(function (el) {
// 各要素をオブザーバーで監視
observer.observe(el);
});
observer.unobserve(entry.target)
をコメントアウトすると、data-visible
の値を切り替えられるようになり、繰り返しアニメーションさせることが可能です。
発火位置を変更してみる
要素が画面のどの位置にきたら発火するかをoptions
に指定し、IntersectionObserverに渡すことで、カスタマイズされたタイミングで動作させることができます。
const options = {
root: null, // ビューポートをルートとして使用
rootMargin: "-10% 0%", // 交差の有効範囲をpx % で指定
threshold: 0, // 要素が何割見えたら発火するかを指定(最小0.0〜最大1.0)
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
entry.target.setAttribute("data-visible", "true");
// 一度監視したら、対象要素の監視を停止(必要に応じてコメントアウト)
observer.unobserve(entry.target);
} else {
entry.target.setAttribute("data-visible", "false");
}
});
}, options);
const viewElements = document.querySelectorAll(".js-in-view");
viewElements.forEach(function (el) {
observer.observe(el);
});
root | 監視対象の交差を検知する枠。null だとビューポートが基準となる。 |
---|---|
rootMargin | rootからのオフセットをpxか%で指定。 デフォルトは 0px 0px 0px 0px (上右下左)。マイナスで内側にオフセットされる。 |
threshold | 要素がどの程度見えたら検知するかの割合。 50%で検知させる場合は0.5を指定。 (最小0.0〜最大1.0) |
rootMarginを"-10% 0%"
にすると、画面内の上下10%までは検知の対象外となります。
個別にアニメーションの動きを上書きしてみる
IntersectionObserverとは直接関係ありませんが、HTML上で各要素ごとに遅延やアニメーションの長さを設定できるように少し工夫します。
アニメーションさせる要素にカスタムデータ属性(data-delay
、data-duration
)を追加して、任意の秒数を指定します。
<div class="js-in-view fadeInUp" data-delay=".25s" data-duration="1s">
各カスタムデータ属性の値をそのままanimation-delay
、animation-duration
へ反映させて、スタイルとして要素に追加すれば完了です。
const options = {
root: null, // ビューポートをルートとして使用
rootMargin: "-10% 0%", // 交差の有効範囲をpx % で指定
threshold: 0, // 0〜1の割合で要素が可視になった時にコールバックを発火
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
entry.target.setAttribute("data-visible", "true");
// 一度監視したら、対象要素の監視を停止(必要に応じてコメントアウト)
observer.unobserve(entry.target);
} else {
entry.target.setAttribute("data-visible", "false");
}
});
}, options);
const viewElements = document.querySelectorAll(".js-in-view");
viewElements.forEach(function (el) {
// delayの上書き
const delay = el.getAttribute("data-delay");
if (delay) {
el.style.animationDelay = delay;
}
// durationの上書き
const duration = el.getAttribute("data-duration");
if (duration) {
el.style.animationDuration = duration;
}
observer.observe(el);
});
CSSの記述例
フェードインアップの例です。
/* スクロールアニメーション
----------------------------------------------- */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
}
// デフォルトのアニメーション設定
[data-visible] {
animation-fill-mode: both;
animation-duration: 2s;
}
// フェードイン系はあらかじめ非表示にしておく
.fadeIn,
.fadeInUp {
opacity: 0;
&[data-visible="true"] {
opacity: 1;
}
}
// アニメーション指定
[data-visible="true"] {
&.fadeInUp {
animation-name: fadeInUp;
}
}
// JSが効かない時のフォールバック(フェードイン系を強制表示)
@keyframes visible {
to {
opacity: 1;
visibility: visible;
}
}
:not([data-visible]).fadeInUp {
animation-name: visible;
animation-fill-mode: forwards; // 最後のキーフレームを保持
animation-duration: 1s;
animation-delay: 1s;
}
シンプルなアニメーションであればtransition
でも十分ですが、バウンドのような複雑なアニメーションを加える可能性があるなら、animation
プロパティに統一しておいた方が無難です。
どちらでも良いですが、2種類のアプローチが混ざるとメンテナンス性が悪くなるので、どちらか一方に統一することをおすすめします。
そして、このCSSで重要なのは、JavaScriptが効かない時のフォールバック処理です。
フェードインアニメーションでは、最初に要素が一瞬見えないようにopacity:0
を設定することがよくありますが、JavaScriptが無効になっているといつまで経っても表示はされません。
それを防ぐために、data-visible
属性の有無でJavaScriptが有効かどうかを判断し、無効時には要素を強制的に表示するようにしています。
HTMLの記述例
<div class="js-in-view fadeInUp" data-delay=".25s" data-duration="1s">
js-in-view
:監視対象(JSで管理)fadeInUp
:任意のアニメーション(CSSで指定)data-delay
:遅延させる秒数(オプション)data-duration
:アニメーションの長さ(オプション)
data-visible
はJavaScriptによって付与するため、直接記述しません。
課題:個別のオフセットを指定したい
まだ良いアプローチが見つかっていないのが、各要素個別にオフセット(どこで発火させるか)を設定する方法です。
理想としては、カスタムデータ属性data-offset
を与え、値が50%であればビューポートの50%の位置で発火というようにしたいんですよね。
各要素ごとにIntersectionObserverを作成すれば実現は可能なのですが、JavaScriptが複雑になることと、パフォーマンスが非常に悪くなるため、一つのIntersectionObserverで管理できれば良いなと。
optionsのrootMarginは、後から動的に変更できないので、IntersectionObserverの中で判定できる方法を模索中です。
おすすめWEBスクール
WEB制作やWEBデザインを学びたいなら、SNSでも話題の「デイトラ」がおすすめ!
どのコースも10万円前後と業界最安値で、副業や転職に向けて十分なスキルを身につけることができます。
\ クリックしてジャンプ! /
お気軽にコメントどうぞ