第一部分 在兩天內在 r/webdev 上達到 69,000 次觀看。我沒有預期到這個結果。我寫它是因為我想取代一個 3.4 MB 的影片,用 40 KB 的 DOM 動畫來實現,覺得這樣的內容夠有趣可以分享。結果發現很多人都在思考同樣的問題.
註解比文章本身更好。人們問到了關於 SEO 索引、螢幕讀取器行為,prefers-reduced-motion 的備用方案,無論 GSAP 是否真的必要,好幾個人指出它缺少了某個東西。他們是對的。
第一部分是一個游標在平坦的舞台上游走過場景。所有事情都發生在同樣的縮放層級,與觀眾同樣的距離。觀看一個真實的產品示範影片,你會注意到一些不同之處:鏡頭會移動。當發生重要的事情時,它會縮放到動作中,跟隨游標通過一個工作流程,當場景變化時會拉回。那就是簡報和導演電影之間的區別。
這篇貼文增加了相機。同樣是40 KB預算。沒有視頻檔案.
參考完整貼文,包含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 |
逐漸顯現 |
| 點擊壓縮 | 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示範會隨產品變化而變化。行銷更改按鈕標籤?更改文字字串。品牌顏色更新?更改一個十六進制值。您希望示範按鈕能實際轉化?將它們連接到一個真正的註冊模態。影片無法做到任何這些。
可訪問性
當 prefers-reduced-motion: reduce 被啟用時,跳過整個時間線並渲染靜態狀態。GSAP的 autoAlpha 設置兩者opacity: 0 和 visibility: hidden,移除從 tab 順序和螢幕讀取器中。每個隱藏的元素使用 autoAlpha,而不是 opacity,所以螢幕讀取器只看到當前可見的部分。
它需要 GSAP嗎?
對於簡單的動畫,不需要。但網頁剪輯示範包含兩個場景中約 50 個協調動畫,包括鏡頭移動。GSAP 提供標籤(命名節拍),repeat: -1 配備重設邏輯,gsap.utils.selector(root) 用於資料屬性查詢,一個完整的彈性函式庫,以及 staggering。你可以不用 GSAP 來建立所有這些。這將花費更長的時間並產生更差的结果。
GSAP 核心 28 KB 壓縮後。如果你正在策劃一個包含 50 個動畫的電影節目示範,它就是基礎設施。
代理現實
站內共有五個示範,大約有4,000行編舞程式碼。我寫了大約60%的那部分。另一40%則由AI代理寫的。
我的60%是導演工作:決定要放大什麼,在觀看兩個選項後選擇eases,發現放大時游標感覺死滯,說「縮小動作慢了200毫秒。」代理無法觀看動畫並感覺到有什麼不對勁。
代理人的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下壓縮後總和不到80 KB。視頻中的等價物將是15-20 MB。
第一部分證明了你可以替換一段影片。第二部分是開始感覺像是指導而非錄製的部分.
前期的成本是實實在在的。持續的成本接近零。更新是程式碼變更,不是重新錄製.
完整文章與互動示範: spanthi.com/blog/gsap-choreography-part-2
製作範例: costumary.com | costumary.com/web-clipper
開源代理技能: gsap-choreography












