スクロールするとふわっと要素が現れる演出は、多くのWebサイトで使われています。昔は scroll イベントで実装していましたが、現在は Intersection Observer API を使うのがスタンダードです。パフォーマンスが良く、コードもシンプルです。
目次
最小構成のコード(これだけでOK)
HTML
<div class="fade-in">ここがふわっと現れます</div>
<div class="fade-in">ここも</div>
<div class="fade-in">これも</div>
CSS
/* 初期状態:非表示 */
.fade-in {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
}
/* JS で追加されるクラス:表示状態 */
.fade-in.is-visible {
opacity: 1;
transform: translateY(0);
}
JavaScript
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
}
});
});
document.querySelectorAll('.fade-in').forEach((el) => {
observer.observe(el);
});
これだけで動きます。外部ライブラリ不要、jQuery不要です。
仕組みの解説
Intersection Observer は「要素が画面内に入ったかどうか」を監視するAPIです。スクロールするたびにすべての要素をチェックする従来の方法と違い、変化があったときだけ処理が走る ので効率的です。
new IntersectionObserver(callback)でオブザーバーを作成observer.observe(el)で監視対象を登録- 要素が画面に入ると
entry.isIntersectingがtrueになる classList.add('is-visible')でアニメーション用クラスを付与
カスタマイズパターン
一度表示したら再度スクロールしても消さない(推奨)
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
observer.unobserve(entry.target); // ← 表示後は監視を解除
}
});
});
カスタマイズポイント
unobserve を追加すると表示後は監視を止めるので、スクロールを戻しても消えません。UX的にはこちらの方が自然です。逆に消えてほしい場合は unobserve を外し、isIntersecting が false のときに classList.remove('is-visible') を追加します。
下からではなく横からスライドイン
/* 左からスライドイン */
.slide-in-left {
opacity: 0;
transform: translateX(-40px);
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
}
.slide-in-left.is-visible {
opacity: 1;
transform: translateX(0);
}
/* 右からスライドイン */
.slide-in-right {
opacity: 0;
transform: translateX(40px);
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
}
.slide-in-right.is-visible {
opacity: 1;
transform: translateX(0);
}
要素ごとに時間差をつける(staggered)
/* CSS で data-delay 属性を使う */
.fade-in[data-delay="1"] { transition-delay: 0.1s; }
.fade-in[data-delay="2"] { transition-delay: 0.2s; }
.fade-in[data-delay="3"] { transition-delay: 0.3s; }
<!-- HTML -->
<div class="fade-in" data-delay="1">先に出る</div>
<div class="fade-in" data-delay="2">少し遅れる</div>
<div class="fade-in" data-delay="3">さらに遅れる</div>
画面の少し手前でトリガーしたい(rootMargin)
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
}
});
},
{
rootMargin: '0px 0px -80px 0px', // 画面下端から80px手前でトリガー
}
);
カスタマイズポイント
rootMargin の第3値(下マージン)をマイナスにすると、要素が画面に完全に入る前にトリガーできます。-100px など値を大きくするほど手前で発火します。
threshold(何割見えたらトリガーするか)
const observer = new IntersectionObserver(
(entries) => { /* ... */ },
{
threshold: 0.3, // 30%見えたらトリガー(0〜1で指定)
}
);
アニメーションの速さ・距離の調整
/* ゆっくり・大きく動く */
.fade-in {
opacity: 0;
transform: translateY(50px); /* 距離を大きく */
transition: opacity 1s ease-out, transform 1s ease-out; /* 時間を長く */
}
/* 素早く・小さく動く */
.fade-in {
opacity: 0;
transform: translateY(10px); /* 距離を小さく */
transition: opacity 0.3s ease-out, transform 0.3s ease-out; /* 時間を短く */
}
モーション低減の設定への対応
アクセシビリティを考慮し、OSのアニメーション低減設定に対応しておくのがおすすめです。
@media (prefers-reduced-motion: reduce) {
.fade-in {
opacity: 1;
transform: none;
transition: none;
}
}
この設定をしておくと、アニメーションが苦手なユーザーにも安心なサイトになります。
