jQueryやScrollMagicに頼らず、Intersection Observer APIだけでスクロール連動アニメーションを実装する実践パターンを紹介。コード例と失敗談も交えて解説します。
こんな悩みはありませんか?
「スクロールしたら要素がふわっと現れるアニメーションを実装したいけど、jQueryのプラグインは重いし、ScrollMagicは設定が複雑で…」
「Intersection Observer APIって聞いたことあるけど、実務でどこまで使えるのか正直わからない」
フロントエンドの現場では、こうした声をよく耳にします。実際、スクロール連動アニメーションはUXを向上させる強力な演出である一方、ライブラリ選定やパフォーマンスの問題で実装が迷走しがちです。
この記事では、Fivenineが実案件で使ってきた Intersection Observer APIのみを使ったスクロールアニメーション実装パターンを具体的なコードと失敗談を交えてご紹介します。外部ライブラリを使わず、ネイティブAPIだけでここまでできるという実感を持っていただけるはずです。
なぜ今、Intersection Observer APIなのか
以前のスクロールイベント(window.addEventListener('scroll', ...))は、スクロールのたびに大量の計算が走り、メインスレッドを圧迫してカクつきの原因になりやすい実装でした。getBoundingClientRect()を毎フレーム呼ぶと強制レイアウトが発生し、FPS低下を招くこともしばしばです。
Intersection Observer APIはこの問題を根本から解決します。要素がビューポートに入ったかどうかをブラウザが非同期で監視してくれるため、開発者側でスクロール量を計算する必要がありません。
主なメリットをまとめると以下の通りです。
- パフォーマンスに優れる: メインスレッドを妨げない非同期処理
- コードがシンプル: 監視・コールバック・解除の3ステップで完結
- ブラウザ対応が広い: Edge、Chrome、Firefox、Safari全対応(IE除く)
- 外部依存ゼロ: ライブラリ不要でバンドルサイズへの影響なし
あるランディングページ案件では、ScrollMagicからIntersection Observer APIへ移行したことで、Lighthouseのパフォーマンススコアが68点から89点に改善した事例があります。アニメーションの演出を落とすことなく、ライブラリを削除するだけでこれだけの差が出るのは、現場で体感して初めてわかる驚きでした。
実践パターン集
パターン1: フェードインアップ(最も汎用的)
スクロールすると要素が下から浮き上がりながらフェードインする、最もよく使われるパターンです。
<!-- HTML -->
<div class="fade-up">コンテンツ</div>
<div class="fade-up">コンテンツ</div>
<div class="fade-up">コンテンツ</div>
/* CSS */
.fade-up {
opacity: 0;
transform: translateY(40px);
transition: opacity 0.6s ease, transform 0.6s ease;
}
.fade-up.is-visible {
opacity: 1;
transform: translateY(0);
}
// JavaScript
const targets = document.querySelectorAll('.fade-up');
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
// 一度表示したら監視を解除してパフォーマンスを維持
observer.unobserve(entry.target);
}
});
}, {
threshold: 0.1, // 10%見えたらトリガー
rootMargin: '0px 0px -50px 0px' // 下端から50px手前で発火
});
targets.forEach((el) => observer.observe(el));
ポイントは observer.unobserve() です。表示後も監視し続けると無駄なコールバックが走り続けるため、一度表示したら即解除するのが鉄則です。
パターン2: 遅延付きスタガーアニメーション
カード要素を順番にずらして表示する「スタガー(千鳥)」アニメーションです。一覧ページやセクションの視覚的な印象が大きく変わります。
// data-delay属性でCSSカスタムプロパティを設定
const cards = document.querySelectorAll('.card');
cards.forEach((card, index) => {
card.style.setProperty('--delay', `${index * 0.1}s`);
});
const staggerObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
staggerObserver.unobserve(entry.target);
}
});
}, { threshold: 0.05 });
cards.forEach((card) => staggerObserver.observe(card));
.card {
opacity: 0;
transform: translateY(30px);
transition:
opacity 0.5s ease var(--delay, 0s),
transform 0.5s ease var(--delay, 0s);
}
.card.is-visible {
opacity: 1;
transform: translateY(0);
}
パターン3: カウントアップアニメーション
数字が視野に入ったタイミングで0からカウントアップする演出です。実績数や統計を印象付けたいセクションで特に効果的です。
<span class="countup" data-target="1500">0</span>
const countupObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
const el = entry.target;
const target = parseInt(el.dataset.target, 10);
const duration = 1500; // ms
const startTime = performance.now();
const tick = (now) => {
const elapsed = now - startTime;
const progress = Math.min(elapsed / duration, 1);
// イージング(ease-out)
const eased = 1 - Math.pow(1 - progress, 3);
el.textContent = Math.floor(eased * target).toLocaleString();
if (progress < 1) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
countupObserver.unobserve(el);
});
}, { threshold: 0.5 });
document.querySelectorAll('.countup')
.forEach((el) => countupObserver.observe(el));
イージング計算に requestAnimationFrame を組み合わせることで、なめらかな数字の変化を実現しています。
よくある失敗パターンと対処法
実案件で何度も遭遇した失敗をまとめました。「なぜか動かない」という場面の大半はこれで解決します。
```css
@media (prefers-reduced-motion: reduce) {
.fade-up {
opacity: 1;
transform: none;
transition: none;
}
}
```
各パターンの活用シーン比較
| パターン | 実装難易度 | モバイル対応 | 推奨シーン |
|---|---|---|---|
| フェードインアップ | 低 | ほぼ全ページ | |
| スタガーアニメーション | 中 | カード・一覧ページ | |
| カウントアップ | 中 | 実績・数値訴求 | |
| パララックス(別途実装) | 高 | LPのファーストビュー |
パララックスは別途 scroll イベントとの組み合わせが必要で、モバイルでのパフォーマンス劣化リスクが高いため、Intersection Observerの守備範囲外として扱うのが現実的です。
Web制作・UI改善もお任せください
フロントエンド開発
モダンな技術で、使いやすく美しいWebサイトを実現します
※ 通常1営業日以内にご返信します
まとめと次のステップ
Intersection Observer APIは、「スクロールイベントの複雑な計算をブラウザに委ねる」という設計思想が秀逸です。コード量が少なく、パフォーマンスが高く、保守しやすい。実務での採用価値は十分あります。
最初の一歩は「フェードインアップ」の1パターンだけを既存プロジェクトに導入してみることです。HTMLに .fade-up クラスを追加して、CSSとJSを数十行書くだけで、サイトの印象は大きく変わります。
ただし、アニメーションの設計はサイト全体のブランドトーンやUXフローと切り離せません。「どこで・何を・どのタイミングで動かすか」の設計を誤ると、かえって煩わしい体験になることも。実装と同時に、演出設計の視点も大切にしてください。
「既存サイトのアニメーション設計を見直したい」「パフォーマンスを落とさずに表現を豊かにしたい」といったご要望があれば、お気軽にFivenine Designまでご相談ください。