
GSAP ScrollTrigger 入門 - 応用編:パララックス、水平スクロール、スナップ
基本編では、ScrollTriggerの基本的な使い方を学びました。
この応用編では、より高度なテクニックを紹介します。パララックス効果や水平スクロール、スナップ機能など、Apple、Nike、Airbnbのような一流サイトで使われているエフェクトを実装していきましょう。これらのテクニックを習得すれば、ユーザーの記憶に残る印象的なスクロール体験を作り出せます。
タイムラインとの組み合わせ
GSAPのタイムライン(timeline)とScrollTriggerを組み合わせると、複数のアニメーションをシーケンシャル(連続的)に制御できます。これは、ストーリーテリング型のコンテンツや、複数ステップの説明ページで特に効果的です。
タイムラインの主な利点:
- 複数のアニメーションを一つのスクロール制御でまとめられる
- 相対的なタイミング制御(前のアニメーションの終了後に次を開始、など)
- ラベルを使った柔軟な位置指定
基本的な使い方
const tl = gsap.timeline({
scrollTrigger: {
trigger: '.container',
start: 'top top',
end: '+=500',
scrub: 1,
pin: true,
},
});
// アニメーションを順番に追加
tl.from('.box1', { x: -100, opacity: 0 })
.from('.box2', { y: 100, opacity: 0 })
.from('.box3', { x: 100, opacity: 0 });このコードでは、スクロールに連動して3つのボックスが順番にアニメーションします。scrub: 1により、スクロール位置に滑らかに追従し、pin: trueでコンテナがビューポートに固定されます。
ラベルを使った制御
タイムラインのラベル機能を使うと、アニメーションの特定の位置に名前を付けることができます。これはsnap機能と組み合わせることで、セクション間のスナップ移動を実現できます。
const tl = gsap.timeline({
scrollTrigger: {
trigger: '.container',
start: 'top top',
end: '+=1000',
scrub: 1,
snap: {
snapTo: 'labels', // ラベル位置にスナップ
duration: 0.3,
},
},
});
tl.addLabel('start')
.from('.step1', { opacity: 0, y: 50 })
.addLabel('step1')
.from('.step2', { opacity: 0, y: 50 })
.addLabel('step2')
.from('.step3', { opacity: 0, y: 50 })
.addLabel('end');snap: { snapTo: 'labels' } を設定すると、スクロールが止まったときに最も近いラベル位置に自動でスナップします。これにより、各ステップの境界で止まる自然な動きを実現できます。
デモ:タイムラインアニメーション
スクロールすると、カードが順番にアニメーションします。スクロールを止めると、各カードの表示位置でスナップします。
このデモでは、ピン留めされたセクション内で複数のカードが順次表示されます。タイムラインとScrollTriggerの組み合わせにより、製品機能の紹介やステップバイステップのチュートリアルなどに最適な表現が可能です。
パララックス効果
パララックス(視差効果)は、複数のレイヤーを異なる速度で動かすことで奥行き感を演出する技術です。遠くのものはゆっくり、近くのものは速く動くという、人間の視覚的経験を模倣しています。
この効果は、ヒーローセクション、背景アニメーション、ストーリーテリング型のランディングページで広く使われています。適切に使えば、ユーザーを引き込む没入感のある体験を作り出せます。
基本的なパララックス
最もシンプルなパララックスは、背景と前景を異なる速度で動かすことで実現できます。
// 背景レイヤー(遅く動く)
gsap.to('.bg-layer', {
y: -100,
scrollTrigger: {
trigger: '.section',
start: 'top bottom',
end: 'bottom top',
scrub: true,
},
});
// 前景レイヤー(速く動く)
gsap.to('.fg-layer', {
y: 100,
scrollTrigger: {
trigger: '.section',
start: 'top bottom',
end: 'bottom top',
scrub: true,
},
});簡単なパララックス関数
複数の要素にパララックスを適用する場合、再利用可能な関数を作っておくと便利です。
function parallax(element, speed) {
gsap.to(element, {
y: () => speed * 100,
ease: 'none',
scrollTrigger: {
trigger: element,
start: 'top bottom',
end: 'bottom top',
scrub: true,
},
});
}
// 使用例
parallax('.layer1', -0.5); // 遅い(奥のレイヤー)
parallax('.layer2', -0.2); // 中間
parallax('.layer3', 0.3); // 速い(手前のレイヤー、逆方向)速度の値の意味:
- 負の値: スクロールと逆方向に動く(通常の背景に使用)
- 正の値: スクロールと同じ方向に動く(前景のアクセントに使用)
- 値が小さいほど: 動きが控えめ(遠い印象)
- 値が大きいほど: 動きが大きい(近い印象)
デモ:パララックス効果
スクロールすると、背景の星や山が異なる速度で動きます。レイヤーごとの速度の違いに注目してください。
このデモでは、複数の背景レイヤーが異なる速度で移動します。遠くの山はゆっくり、近くの装飾は速く動くことで、立体的な奥行き感を表現しています。
水平スクロール
水平スクロールは、縦方向のスクロールで横方向にコンテンツを動かす印象的なエフェクトです。ポートフォリオ、製品ショーケース、タイムライン表示などでよく使われ、通常のスクロールとは異なる新鮮な体験を提供できます。
この技術は、Apple製品のページやクリエイティブエージェンシーのWebサイトで頻繁に見られます。縦スクロールという馴染みのある操作で横方向の動きを体験できるため、ユーザーにとって直感的でありながら印象的です。
基本的な水平スクロール
水平スクロールの実装は、ピン留めとX軸の移動を組み合わせます。
const track = document.querySelector('.horizontal-track');
const items = track.querySelectorAll('.item');
// トラックの総幅を計算
const totalWidth = items.length * 300; // 各アイテム300px
gsap.to(track, {
x: -totalWidth + window.innerWidth, // 画面幅分を残す
ease: 'none',
scrollTrigger: {
trigger: '.horizontal-container',
start: 'top top',
end: () => `+=${totalWidth}`,
pin: true,
scrub: 1,
},
});デモ:水平スクロール
スクロールすると、カードが横方向に流れます。縦スクロールと横移動の連動を体験してください。
このデモでは、ポートフォリオ風のカードが横方向に流れます。縦のスクロール量が横の移動量に変換されている点に注目してください。
水平スクロール内でのアニメーション
水平スクロール内の各要素にもアニメーションを追加したい場合は、containerAnimationオプションを使用します。これにより、水平スクロールの動きに連動した個別のアニメーションが可能になります。
水平スクロール内の要素にアニメーションを追加する場合は、containerAnimation を使います。
const horizontalTween = gsap.to('.track', {
x: -2000,
ease: 'none',
scrollTrigger: {
trigger: '.container',
start: 'top top',
end: '+=2000',
pin: true,
scrub: 1,
},
});
// 水平スクロール内の各アイテムにアニメーション
gsap.utils.toArray('.item').forEach((item) => {
gsap.from(item, {
scale: 0.8,
opacity: 0.5,
scrollTrigger: {
trigger: item,
containerAnimation: horizontalTween, // 親アニメーションを指定
start: 'left center',
end: 'right center',
scrub: true,
},
});
});スナップ機能
スナップ機能を使うと、スクロールが止まったときに特定の位置に「吸い付く」ように自動で移動します。これは、セクション間のスムーズな移動や、フルスクリーンのスライド体験を実現するのに最適です。
スナップは、ユーザーが中途半端な位置で止まってしまうのを防ぎ、コンテンツが最も見やすい位置に自動調整します。製品紹介ページ、プレゼンテーション風のWebサイト、ワンページサイトなどで効果的です。
数値でスナップ
最もシンプルな方法は、進捗の割合で指定する方法です。
scrollTrigger: {
// 10%刻みでスナップ
snap: 0.1,
}配列でスナップ
scrollTrigger: {
// 特定の位置にスナップ
snap: [0, 0.25, 0.5, 0.75, 1],
}関数でスナップ
scrollTrigger: {
snap: (value) => {
// 最も近い0.2の倍数にスナップ
return Math.round(value / 0.2) * 0.2;
},
}詳細なスナップ設定
より細かいスナップの動作を制御したい場合は、オブジェクト形式で詳細設定ができます。
scrollTrigger: {
snap: {
snapTo: 'labels', // タイムラインのラベルにスナップ
duration: 0.3, // スナップアニメーションの時間
delay: 0.1, // スクロール停止後の待機時間
ease: 'power1.inOut', // イージング
directional: true, // スクロール方向を考慮
},
}各プロパティの説明:
| プロパティ | 説明 | おすすめ値 |
|---|---|---|
snapTo | スナップ先(数値、配列、関数、'labels') | 用途による |
duration | スナップアニメーションの時間 | 0.2〜0.5秒 |
delay | スクロール停止から動き出すまでの待機時間 | 0〜0.2秒 |
ease | スナップ移動のイージング | 'power1.inOut' |
directional | スクロール方向を考慮するか | true |
デモ:スナップ
スクロールすると、セクション間でスナップします。スクロールを止めると、最寄りのセクションに自動で吸い付く動きを確認してください。
このデモでは、各セクションの境界でスナップが発生します。フルスクリーンのスライドショーやポートフォリオのような体験を、通常のスクロール操作で実現しています。
Stagger アニメーション
Stagger(スタガー)は、複数の要素を時間差で順番にアニメーションさせるテクニックです。リスト、グリッド、カードなどの要素が「波のように」表示される効果は、ユーザーの目を引き、コンテンツの構造を視覚的に伝えるのに非常に効果的です。
staggerは、スクロールで画面に入ったときの表示アニメーションとして広く使われています。すべての要素が同時に現れるよりも、順番に現れる方がはるかにエレガントで、ユーザーの注目を集めやすくなります。
基本的なstagger
最もシンプルなstaggerは、staggerプロパティに数値を渡すだけです。
gsap.from('.item', {
y: 50,
opacity: 0,
stagger: 0.1, // 0.1秒ずつずらして開始
scrollTrigger: {
trigger: '.container',
start: 'top 80%',
},
});グリッドでのstagger
グリッドレイアウトでは、gridオプションを使うことで2次元的なstaggerを実現できます。中心から広がる、角から広がるなど、様々なパターンが可能です。
gsap.from('.grid-item', {
scale: 0.5,
opacity: 0,
stagger: {
each: 0.1, // 各要素の遅延
grid: [4, 3], // 4列 x 3行のグリッド
from: 'center', // 中心から広がる
},
scrollTrigger: {
trigger: '.grid',
start: 'top 70%',
},
});stagger の from オプション
fromオプションで、アニメーションがどこから始まるかを指定できます。
| 値 | 説明 | 用途例 |
|---|---|---|
'start' | 最初の要素から | リスト、通常の順序 |
'end' | 最後の要素から | 逆順の表示 |
'center' | 中央から外側へ | インパクトのある登場 |
'edges' | 両端から中央へ | 収束する動き |
'random' | ランダム順 | 有機的な印象 |
0 | インデックス0から | 明示的な指定 |
[0.5, 0.5] | グリッドの中心座標 | グリッド用(0-1の範囲) |
デモ:Stagger
スクロールすると、グリッドアイテムが順番に表示されます。中心から広がるようなアニメーションパターンに注目してください。
このデモでは、グリッドの中心から外側に向かって要素が順番に表示されます。このような動きは、ポートフォリオギャラリー、商品一覧、チームメンバー紹介などで効果的に使えます。
コールバック関数
ScrollTriggerは、様々なタイミングでコールバック関数を実行できます。これにより、アニメーション以外の処理(分析イベントの送信、状態の更新、DOM操作など)をスクロールに連動させることができます。
コールバックは、プログレスバーの更新、遅延読み込み、動的なコンテンツ変更など、インタラクティブな機能の実装に欠かせない機能です。
利用可能なコールバック
ScrollTriggerは以下のコールバックをサポートしています。
ScrollTrigger.create({
trigger: '.element',
start: 'top center',
end: 'bottom center',
// トリガー位置を通過したとき
onEnter: (self) => console.log('Enter:', self.progress),
onLeave: (self) => console.log('Leave:', self.progress),
onEnterBack: (self) => console.log('Enter Back:', self.progress),
onLeaveBack: (self) => console.log('Leave Back:', self.progress),
// 状態が変化したとき
onToggle: (self) => console.log('Active:', self.isActive),
// スクロール中(毎フレーム)
onUpdate: (self) => {
console.log('Progress:', self.progress);
console.log('Direction:', self.direction); // 1: 下, -1: 上
console.log('Velocity:', self.getVelocity());
},
// リフレッシュ時
onRefresh: (self) => console.log('Refreshed'),
// scrubアニメーション完了時
onScrubComplete: () => console.log('Scrub complete'),
});実用例:プログレスバー
const progressBar = document.querySelector('.progress-bar');
ScrollTrigger.create({
trigger: 'article',
start: 'top top',
end: 'bottom bottom',
onUpdate: (self) => {
progressBar.style.width = `${self.progress * 100}%`;
},
});パフォーマンス最適化
ScrollTriggerを使った複雑なアニメーションは、適切に最適化しないとパフォーマンスの問題を引き起こす可能性があります。特にモバイルデバイスでは、スクロールのカクつきや遅延がユーザー体験を大きく損なうため、以下のベストプラクティスを意識しましょう。
1. will-change の適切な使用
ブラウザに対して、どのプロパティがアニメーションするかを事前に伝えることで、GPU最適化を促せます。
.animated-element {
will-change: transform, opacity;
}⚠️ 注意: will-changeの過度な使用はメモリを消費します。アニメーションが終わったらwill-change: autoにリセットすることをおすすめします。
2. イベントの最適化
ScrollTriggerの自動リフレッシュイベントを必要なものだけに制限することで、不要な再計算を防げます。
ScrollTrigger.config({
autoRefreshEvents: 'visibilitychange,DOMContentLoaded,load',
});3. 不要なScrollTriggerの削除
// 特定のScrollTriggerを削除
myScrollTrigger.kill();
// すべてのScrollTriggerを削除
ScrollTrigger.getAll().forEach((st) => st.kill());4. バッチ処理
多数の要素にScrollTriggerを適用する場合は、ScrollTrigger.batch()を使うと効率的です。個別のScrollTriggerを大量に作成するよりも、パフォーマンスが向上します。
// 複数の要素を効率的に処理
ScrollTrigger.batch('.item', {
start: 'top 80%',
onEnter: (elements) => {
gsap.to(elements, {
opacity: 1,
y: 0,
stagger: 0.1,
});
},
onLeave: (elements) => {
gsap.to(elements, {
opacity: 0,
y: 20,
});
},
});batch()は同時に画面に入った複数の要素を一括で処理するため、個別のScrollTriggerより効率的です。ブログの記事リストや商品カードなど、多数のアイテムがある場合に特に効果的です。
よくある問題と解決策
ScrollTriggerを使っていると、様々な問題に遭遇することがあります。ここではよくある問題とその解決策をまとめました。
問題1: アニメーションの開始位置がずれる
原因: 画像の読み込み前に位置を計算している
解決策:
// 画像読み込み後にリフレッシュ
window.addEventListener('load', () => {
ScrollTrigger.refresh();
});
// または
ScrollTrigger.config({
autoRefreshEvents: 'visibilitychange,DOMContentLoaded,load,resize',
});問題2: モバイルでカクつく
原因: アニメーションが重い
解決策:
// モバイルではアニメーションを簡略化
const isMobile = window.matchMedia('(max-width: 768px)').matches;
gsap.to('.box', {
x: isMobile ? 100 : 300, // モバイルは控えめに
scrollTrigger: {
scrub: isMobile ? true : 0.5, // スムージングを減らす
},
});問題3: ピン留め中にレイアウトが崩れる
原因: CSSの問題
解決策:
scrollTrigger: {
pin: true,
pinSpacing: false, // 余白を無効化
anticipatePin: 1, // ピン留めを先読み
}まとめ
この応用編では、ScrollTriggerの高度な機能を学びました。
学んだこと
- タイムラインとの組み合わせ
- パララックス効果の実装
- 水平スクロールの作成
- スナップ機能
- Stagger アニメーション
- コールバック関数の活用
- パフォーマンス最適化
実践のヒント
- シンプルに始める: まずは基本的なアニメーションから
- 段階的に追加: 複雑なエフェクトは少しずつ
- パフォーマンスを意識: 特にモバイル対応は重要
- デバッグを活用:
markers: trueで位置を確認
これらのテクニックを組み合わせることで、印象的なスクロール体験を作り出すことができます。