第一部到達しました69,000r/webdevに関する見解を2日でまとめました。思わなかったです。40KBのDOMアニメーションで3.4MBの動画を置き換えることを書いたのは、それが共有に価値があると思ったからです。多くの人々が同じ問題について考えていることがわかりました。
コメントは投稿よりも良かった。人々はSEOインデックスングやスクリーンリーダーの挙動について尋ねた。prefers-reduced-motion のフォールバックについて、GSAP が本当に必要かどうか、そして数人が何かが足りないと指摘した。彼らは正しかった。
第一部では、平坦なステージ上でカーソルがシーンをクリックしていました。すべての動作は同じズームレベルで、観客から同じ距離で起こりました。実際の製品デモビデオを見ると、違う点に気づきます:カメラが動きます。重要なことが起こるとアクションにズームインし、ワークフローをカーソルに追従し、シーンが変わると引き戻ります。これがスライドデッキと指揮された映画の違いです。
この投稿ではカメラを追加しました。同じ40KBの予算です。ビデオファイルはありません.
4つのインタラクティブデモ、横並び比較、ライブFPSストレステストを含む完全な投稿を見てください
ズームラッパーの問題
最初に試すのはgsap.to(frame, { scale: 1.4 })です。半秒ほど動作しますが、その後レスポンシブスケーリングが消失するのを確認します。フィルムフレームはtransform: scale(filmScale) があり、GSAP はそれを上書きしました。両方は同じ CSS transform プロパティに書き込みます。同じ要素上で共存できません。
修正は、GSAP が所有するフレーム内のラッパーです。
<div data-film-frame style={{ transform: `scale(${filmScale})` }}>
<div data-film-zoom className="origin-center">
{/* All scene content */}
</div>
<Cursor /> {/* Outside the zoom wrapper */}
</div>
GSAP がアニメーションを実行data-film-zoom 。外枠のレスポンシブスケールは変更されていません。カーソルはズームラッパーの外にあり、コンテンツがそれを囲んで拡大する際にサイズが同じままです。これを理解するのに、恥ずかしいほどの時間がかかりました。二つのdivです。それが全ての修正でした。
。 パンマス
フレームの中心にズームするのは簡単です。Pinterestのピンのような特定の要素にズームするのは実際の問題です。ズームラッパーを変換して、ターゲットが表示されるビューポートの中心に配置する必要があります:
const ZOOM = 1.45;
const panTo = (target) => ({
x: (FILM_WIDTH / 2 - target.x) * (ZOOM - 1),
y: (FILM_HEIGHT / 2 - target.y) * (ZOOM - 1),
});
ズーム1.0では翻訳は不要です。ズーム1.45では、中心から離れたピクセルはその距離の0.45倍の翻訳が必要です。
ズームを維持し、パンで追跡
最初の試みで最も大きな間違いは、一度のインタラクションでズームインし、ズームアウト、次のインタラクションで再びズームインし、再びズームアウトすることでした。それは予算の限られたズームトランジションのPowerPointのようでした。
正しいパターン: 一度ズームインし、ズームを維持し、カーソルを追って全てのインタラクションシーケンスにパンし、シーンを変更する際に一度ズームアウトする.
// Zoom in on right-click
tl.to(zoom, {
scale: ZOOM, x: panPin.x, y: panPin.y,
duration: 0.65, ease: "expo.out",
}, "zoomIn");
// Context menu while zoomed
tl.to(ctx, { autoAlpha: 1, duration: 0.16 }, "zoomIn+=0.35");
// Camera pans to follow cursor to the save button (still zoomed)
tl.to(zoom, {
x: panSave.x, y: panSave.y,
duration: 0.55, ease: "sine.inOut",
}, "panToSave");
// Only zoom out when done
tl.to(zoom, {
scale: 1, x: 0, y: 0,
duration: 0.45, ease: "sine.inOut",
}, "zoomOut");
イージングはすべてに適したものではない
第一部は使用されたpower2.out はほぼすべてのことに使えます。フラットなデモにはいいですが、カメラの動きを加えると、異なる動きのタイプには異なるイージングが必要で、そうしないと全体が不自然に感じられます。
| 動き | イージング | なぜ |
|---|---|---|
| カメラの回転 | sine.inOut |
最も滑らかな曲線。本物のカメラがドーリーに乗っているような感覚です。 |
| 効果的なズームイン | expo.out |
早い始まり、長い減速。ブシュッと音が鳴り、落ち着きます。 |
| カーソルの移動 | sine.inOut |
自然な手の加速度 |
| フェードイン | sine.out |
可視化に減速 |
| クリックスqueeze | power2.out |
反応が速い押し付け |
| クリックの解放 | back.out(2.2) |
わずかなオーバーシュート。物理的な感覚 |
| タイプされたテキスト | none |
実際のタイピングが楽にならない |
最も大きなアップグレードは、カメラの動きにおいてpower2.inOutをsine.inOutに置き換えたことです。power2には、大きなスケールで機械的だと感じる目立ちやすい加速があります。sineは、曲線のどこにいても認識できるようなキックはありません。
カーソルは決して無効になりません
カメラの動きにおいて、シーンが切り替わる瞬間でカーソルが固着している場面があります。それらの瞬間はイリュージョンを壊します。各カメラの動きには、同じタイムラインラベルでの同時のカーソルの動きが必要です:
// BAD: cursor is dead during zoom
tl.to(zoom, { scale: 1.4, x: panX, y: panY, duration: 0.7 }, "zoomIn");
// GOOD: cursor drifts toward its next target during zoom
tl.to(zoom, {
scale: 1.4, x: panX, y: panY,
duration: 0.7, ease: "expo.out",
}, "zoomIn");
tl.to(cursor, {
x: current.x + (next.x - current.x) * 0.3,
y: current.y + (next.y - current.y) * 0.3,
duration: 0.65, ease: "sine.out",
}, "zoomIn");
カーソルが到着する必要はありません。次のターゲットへの20~30%のずれで十分です。落ち着いていますが、決して死んでいません。
全体的投稿には、ズーム中のフリーズしたカーソルとドリフトするカーソルの横並び比較があります
優雅なループ
第一部のループは急な動きでした。カメラの動きでは問題が悪化します。ズームが1.4倍から1.0倍に瞬時にジャンプするからです
対処法はアウトロです
tl.to(zoom, { scale: 1, x: 0, y: 0, duration: 0.8, ease: "sine.inOut" }, "outro");
tl.to(cursor, { x: offScreenX, y: offScreenY, duration: 0.7, ease: "sine.in" }, "outro+=0.1");
tl.to({}, { duration: 0.6 }); // breathing room
タイミングに関するルール:最初の草稿は常に遅すぎる。最初の動作するバージョン後には、タイミングから20-30%削減する.
SEO、テーマング、そして動画ができないこと
<video>タグでソースを表示してGoogleが見る<video src="demo.mp4">。黒い箱。GSAPデモでソースを確認すると、すべてのボタンのラベル、メニュー項目、ヘッディングがインデックス可能なテキストです。制御されたランキングテストは行っていませんが、論理的には:実際のDOMテキストは不透明なバイナリボリュームを上回ります。
インデックス化を超えて、製品が変わるたびにDOMデモが変わります。マーケティングがボタンのラベルを変更した?テキスト文字列を変更します。ブランドカラーの更新?1つの16進値です。デモのボタンが実際にコンバートするようにしたい?実際のサインアップモーダルに接続します。ビデオはそれらを何もできない
アクセシビリティ
prefers-reduced-motion: reduceが有効な場合、タイムライン全体をスキップし、静止状態をレンダリングします。GSAPのautoAlphaは両方を設定しますopacity: 0 と visibility: hidden をタブ順序とスクリーンリーダーから削除します。隠れた要素はすべて autoAlpha を使用し、opacity は使用しません。そのため、スクリーンリーダーは現在表示されているものだけを見ます。
GSAPが必要ですか?
単純なアニメーションの場合は不要ですが、ウェブクリッパーデモにはカメラの動きを含む2つのシーンで約50個の連携アニメーションがあります。GSAPはあなたにラベル(名前付きビート)を提供します。repeat: -1、データ属性クエリ用のgsap.utils.selector(root)、完全なイージングライブラリ、およびステッカー。これらをすべてGSAPなしで構築することもできます。もっと時間がかかり、結果も悪くなります。
GSAPのコアは28 KB gzippedです。50アニメーションを含むシネマティックデモをオーケストレートしている場合、それはインフラストラクチャです。
エージェント現実
サイト全体で5つのデモ、約4,000行の振り付けコードです。そのうち約60%は私が書きました。残りの40%はAIエージェントが書きました
私の60%は監督で、ズームインするものを決めたり、両方のオプションを見てスムーズ化を選んだり、ズームイン中にカーソルが動きにくいことに気づいたり、「ズームアウトが200ms遅すぎる」と言ったりしました。エージェントはアニメーションを見て何かがおかしいと感じることはできません。
エージェントの40%は実装でした:タイムラインコード1,500行、DOM要素、クリックヘルパー、リセットブロック。スキルファイルで定義したルールに従ったパターンに基づく作業。パターンは十分に安定しているため、異なるデモ間で出力が一貫しています。しかし、エージェントは何かが間違っているかどうかを知ることはできません。その部分はまだ私のものです.
パフォーマンス
ウェブクリッパーのデモには50 .to()の呼び出しがありますが、フレームごとに3-5つだけアクティブにトゥイーンしています。毎回.to()はtransformまたはopacityに書き込み、どちらもコンポジターフレンドリーです。フルポストにはFPSカウンター付きのライブストレステストがあり、8から48の並列要素を押し込み、フレームレートを監視できます。更新された数学
| アプローチ | 圧縮 | カメラ? | インデックス可能? | アクセス可能? | テーマ可能? |
|---|---|---|---|---|---|
| GSAP(フラット、第一部) | ~37 KB | いいえ | はい | はい | はい |
| GSAP(パン&ズーム、第二部) | ~40 KB | はい | はい | はい | はい |
| 15秒 MP4 (H.264) | 2-4 MB | 内蔵 | いいえ | いいえ | いいえ |
| 15秒 WebM (VP9) | 1-2 MB | 内蔵 | いいえ | いいえ | いいえ |
| 15秒 GIF | 8~15 MB | 焼き込まれて | いいえ | いいえ | いいえ |
カメラを追加すると、バンドルに約3 KBが追加されます。costumary.comの全5つの生産デモが圧縮後合計80 KB未満です。これに相当する動画のサイズは15~20 MBです。
第一部は動画を置き換えられることを証明した。第二部はそれが録画ではなく指示されたように感じ始める部分だ。
初期コストは現実的。継続コストはほぼゼロ。アップデートはコードの変更ではなく再録画ではない。
インタラクティブデモを含む全文: spanthi.com/blog/gsap-choreography-part-2
プロダクション例: costumary.com | costumary.com/web-clipper
オープンソースエージェントスキル: gsap-choreography












