ページを開いた瞬間にすべての情報が一度に表示されるサイトと、スクロールするたびにコンテンツがふわっと現れるサイト。どちらが「読みたい」と感じるでしょうか?
スクロール連動のアニメーションは、ユーザーの視線を誘導し、コンテンツに「ストーリー性」を生み出す強力なテクニックです。本記事では Intersection Observer API とCSSを組み合わせた4つのパターンを、コピペ可能なコードで解説します。
なぜスクロールアニメーションが効果的なのか
人間の目は「動くもの」に自然と引きつけられます。スクロールに合わせてコンテンツが現れると、ユーザーは無意識のうちにそのコンテンツに注目します。これは特に、サービス紹介や実績紹介など「順序立てて読んでほしい」コンテンツに効果的です。
ただし、やりすぎは逆効果。スクロールのたびに派手なアニメーションが走ると、肝心のコンテンツが頭に入ってきません。「気づかないくらいが、ちょうどいい」が鉄則です。
共通の仕組み:Intersection Observer API
かつてはスクロール位置を scroll イベントで毎フレーム監視するしかありませんでしたが、現在は Intersection Observer API が主流です。要素がビューポートに入った瞬間だけコールバックが走るため、パフォーマンスに優れています。
以下のJavaScriptを1つ書いておけば、4つすべてのパターンに対応できます。
// すべてのスクロールアニメーション対象を監視
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
}
});
}, {
threshold: 0.15, // 15%見えた時点で発火
rootMargin: '0px 0px -50px 0px'
});
document.querySelectorAll('.fade-in, .slide-left, .slide-right, .scale-in, .stagger-item')
.forEach(el => observer.observe(el));
threshold: 0.15 は「要素の15%がビューポートに入ったら」を意味します。値を大きくすると、より深くスクロールしないと発火しません。0.1〜0.2 がバランスの良い値です。
パターン1:フェードイン + 上方スライド
最も定番で汎用性の高いパターンです。要素が下から30px浮き上がりながらフェードインします。セクションタイトルやテキストブロックに最適です。
<div class="box fade-in">フワッと下から出現 ✨</div>
/* ===== ベースのレイアウト(デザインに合わせて調整してください) ===== */
.box {
/* 本体のスタイル */
}
/* ===== 動作のアニメーション ===== */
.fade-in {
opacity: 0;
transform: translateY(30px);
transition: opacity 0.8s ease,
transform 0.8s cubic-bezier(0.4, 0, 0.2, 1);
}
.fade-in.is-visible {
opacity: 1;
transform: translateY(0);
}
パターン2:左右からのスライドイン
2カラムレイアウトで、左の要素は左から、右の要素は右からスライドインさせると、コンテンツに「出会い」の印象が生まれます。ビフォーアフターや比較表示にぴったりです。
<div class="box slide-left">← 左からスライドイン</div> <div class="box slide-right">右からスライドイン →</div>
/* ===== ベースのレイアウト(デザインに合わせて調整してください) ===== */
.box {
/* 本体のスタイル */
}
/* ===== 動作のアニメーション ===== */
.slide-left {
opacity: 0;
transform: translateX(-40px);
transition: opacity 0.7s, transform 0.7s cubic-bezier(0.4, 0, 0.2, 1);
}
.slide-left.is-visible {
opacity: 1;
transform: translateX(0);
}
.slide-right {
opacity: 0;
transform: translateX(40px);
transition: opacity 0.7s, transform 0.7s cubic-bezier(0.4, 0, 0.2, 1);
}
.slide-right.is-visible {
opacity: 1;
transform: translateX(0);
}
パターン3:スケールイン(拡大して登場)
要素が少し小さい状態から原寸大に拡大しながら出現します。ヒーロー画像やロゴの登場に使うと、存在感のある印象になります。
<div class="box scale-in">ポンッと拡大して登場 🎯</div>
/* ===== ベースのレイアウト(デザインに合わせて調整してください) ===== */
.box {
/* 本体のスタイル */
}
/* ===== 動作のアニメーション ===== */
.scale-in {
opacity: 0;
transform: scale(0.9);
transition: opacity 0.6s,
transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.scale-in.is-visible {
opacity: 1;
transform: scale(1);
}
cubic-bezier(0.34, 1.56, 0.64, 1) は「少しだけ行き過ぎてから戻る」イージング。バウンス感のある気持ちいい動きになります。
パターン4:スタガー(時間差)アニメーション
複数の要素がパラパラと順番にフェードインする「スタガーアニメーション」。カードの一覧やチームメンバーの紹介に使うと、リズム感のある美しい画面が生まれます。
<div class="stagger-grid">
<div class="stagger-item" style="transition-delay: 0s;">01</div>
<div class="stagger-item" style="transition-delay: 0.1s;">02</div>
<div class="stagger-item" style="transition-delay: 0.2s;">03</div>
<div class="stagger-item" style="transition-delay: 0.3s;">04</div>
<div class="stagger-item" style="transition-delay: 0.4s;">05</div>
<div class="stagger-item" style="transition-delay: 0.5s;">06</div>
</div>
/* ===== ベースのレイアウト(デザインに合わせて調整してください) ===== */
.stagger-grid {
/* 本体のスタイル */
}
/* ===== 動作のアニメーション ===== */
.stagger-item {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.5s, transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
.stagger-item.is-visible {
opacity: 1;
transform: translateY(0);
}
/* 各要素にHTMLで transition-delay を設定 */
.stagger-item:nth-child(1) { transition-delay: 0s; }
.stagger-item:nth-child(2) { transition-delay: 0.1s; }
.stagger-item:nth-child(3) { transition-delay: 0.2s; }
/* ...以降も 0.1s ずつ加算 */
まとめ:スクロールに「語り」を添える
スクロールアニメーションの目的は「派手さ」ではなく、ユーザーの視線を自然に導き、コンテンツに物語性を持たせることです。今回紹介した4パターンをまとめます。
- フェードイン + 上方スライド → 最も汎用的。迷ったらこれ
- 左右からのスライド → 2カラム構成の対比に
- スケールイン → インパクトを出したい画像やロゴに
- スタガー → カード一覧でリズム感を演出
共通する鉄則は「移動量は20〜40px以内に抑える」「所要時間は0.5〜0.8秒」の2点。大きく動かしすぎると酔いの原因になり、遅すぎるとテンポが悪くなります。
prefers-reduced-motion: reduce メディアクエリで、ユーザーの設定に応じてアニメーションを無効化しましょう。@media (prefers-reduced-motion: reduce) { .fade-in, .slide-left, .slide-right, .scale-in { transition: none; opacity: 1; transform: none; } }