Appleスタイルのスクロールアニメーション
エフェクトはスクロール位置によって進行し、タイマーでトリガーされるわけではありません。上にスクロールすると逆再生されます。ライブラリは不要です。
3つのアイデア
- ピン留め 背の高いラッパー(例:
height: 300vh)が、position: sticky; top: 0; height: 100vh; overflow: hiddenの子要素を保持します。子要素は画面に留まり、ラッパーがスクロールして通過します。ラッパーの余分な高さがアニメーションの予算です。 - 進行度 1つの関数でスクロール位置を0..1の数値に変換します:
- ピン留めセクション:
clamp(-rect.top / (rect.height - innerHeight)) - ビューポートに入る要素:
clamp((innerHeight*a - rect.top) / (innerHeight*(a-b)))
- ピン留めセクション:
- 数値から視覚効果を駆動 各フレームで
translate、rotate、scale、opacity(およびborderRadius)を設定します。1つのscrollリスナーをrequestAnimationFrameでスロットルしてすべてを再計算します。
すぐに貼り付け可能なエンジン:references/scroll-engine.js
効果的なエフェクト(すべてではなく3~5個選ぶこと)
| エフェクト | 方法 |
|---|---|
| 組み立て | アイテムをビューポートの端にランダムに配置(ランダムな角度、回転、スケール .35、透明度 .2)し、レイアウト位置にイーズアウトします。各アイテムの自然な位置からのオフセットをロード時/リサイズ時に1度計算し、(1 - ease(p)) を掛けます。インデックスで遅延させます。 |
| 単語発光 | 各単語をspanでラップします。clamp(p * 1.25 * N - k) として透明度 .18 → 1 に変化させます。 |
| ピン留めスッテパー | step = floor(p * N);少数部分 p*N - step がアクティブなステップ内の進行バーを埋めます。ステップをクリックするとそのステップの位置に scrollTo します。 |
| 傾きフラット | 端末/デバイスのモックアップに対して perspective(1400px) rotateX((1-e)*22deg) translateY((1-e)*60px) scale(.84 + .16*e) を適用します。 |
| カード浮上 | translate: 0 (1-e)*80px; scale: .9+.1e; opacity: e、列ごとに遅延させます(i % cols)。 |
| 拡大 | ビデオ/画像の scale .78 → 1、borderRadius 44px → 28px。 |
進行度を適用する前に ease = t => 1 - (1-t)**3 を使用します。
ルール(苦労して学んだこと)
- 個別のCSSプロパティ
translate、rotate、scaleを進行度の適用に使い、transformはホバー用に残しておきます。そうしないとホバーとスクロールが同じプロパティを競合します。 - 同じ要素に
.revealフェードクラスと進行度駆動の変換を併用しないでください。どちらか一方を選びます。 - セクションの見出しはコンテンツと一緒にピン留めパネル内に配置します。パネルにコンテンツだけが中央に配置されていると、背の高いモニターでは大きな空白領域が表示されます。
- スクロール距離は控えめに。Nステップのスッテパーでは約
N * 55vh + 100vh、単発のエフェクトでは240-300vhにします。700vh以上はユーザーが疲れます。 - ピン留めするものは100vhに収まるものに制限します。短い画面(
max-height: 820px)やスマートフォンでは、字幕や追加要素を非表示にし、スッテパーではアクティブなステップのみ表示します。 - 動きの抑制:
prefers-reduced-motion: reduceの場合、すべての位置で進行度1を返します(最終状態、動きなし)。要素を非表示のままにしないでください。 - Stickyは祖先に
overflowを必要としません ただしbodyのoverflow-x: hiddenは問題ありません。sticky要素とページの間にあるラッパーにoverflow: hiddenを設定しないでください。 - リサイズ時に再計算します(組み立てオフセットはレイアウトに依存します)。
translate/rotate/scale/opacity/border-radiusのみをアニメーション化し、レイアウトプロパティは避けます。- ビデオの最初と最後のごく短いセグメントは避けます:
<video>の再生をスクラブする場合、短いクリップは空白を表示する可能性があります。
標準的なページ順序
ヒーロー(ピン留め、組み立て)→ プレーンなステートメント(エフェクトなし)→ 証明/フィルム(拡大)→ ピン留めスッテパー → デバイスモックアップ(傾きフラット)→ カードグリッド(浮上)→ CTA。Appleらしい雰囲気のために黒と薄いグレーのセクションを交互に配置し、アクセントカラーを1つ使用します。
テスト
ヘッドレスChromeの --screenshot はスクロールできません。代わりにDevToolsプロトコルを介して実際のChromeを操作します:
chrome --headless=new --remote-debugging-port=9333 --user-data-dir=/tmp/prof about:blank &- Node(v22以降にはグローバルな
WebSocketがあります)から:http://localhost:9333/jsonをfetchし、ページのwebSocketDebuggerUrlを開き、Emulation.setDeviceMetricsOverride、Page.navigateを送信し、各位置に対してRuntime.evaluate("scrollTo(0,Y)")を実行し、約700ミリ秒待機し、Page.captureScreenshotを実行します。 - 各ピン留めセクションの
offsetTopと高さからYを選択します(例:0%、60%、100%)。 - デスクトップ(1440x900)、大きな画面(2000x1300)、スマートフォン(390x800)で確認し、
document.documentElement.scrollWidth === innerWidth(水平方向のオーバーフローがないこと)を確認します。
納品チェックリスト
- すべてのエフェクトがスクロール進行度で駆動され、逆方向にも動作すること
- 動きの抑制(reduced-motion)のフォールバック
- スマートフォンレイアウトの確認、水平スクロールがないこと
- ピン留めスクロール長が控えめであること、背の高い画面で空白がないこと
- 各ピン留めセクションの開始/中間/終了でスクリーンショットを取得すること