前幾天我們從 GSAP 的 Tween、Timeline 一路講到 ScrollTrigger,動畫已經可以跟著按鈕或捲動播放。
但實際把動畫放進 Vue 元件並產生互動後,還必須考慮兩件事,第一個就是我們再 day 1 有提過的「清除動畫」,第二的就是進行互動時,像是游標位置這種隨時再進行的互動,需要做高頻更新。
今天我們就來整理 GSAP 動畫的管理方式。第一個實驗可以直接看到 context.kill() 與 context.revert() 留下的畫面差異;第二個實驗則能切換兩種游標的跟隨方式 quickTo() 和 quickSetter()。
老樣子,我們可以搭配 demo 來實際操作~ 👉🏿 demo連結
gsap.context() 可以把 callback 內建立的 GSAP 動畫歸在同一組管理:
context = gsap.context(() => {
const samples = gsap.utils.toArray(".day-06-sample");
timeline = gsap.timeline({
paused: true,
repeat: -1,
yoyo: true,
defaults: { duration: 1.15, ease: "power2.inOut" },
});
timeline
.to(samples[0], { x: 76, rotation: 180, backgroundColor: "#c97958" }, 0)
.to(samples[1], { y: -38, scale: 1.35, backgroundColor: "#b38a4a" }, 0.12)
.to(samples[2], { x: -70, rotation: -135, scale: 0.76 }, 0.24);
}, stageRef);
最外層先把 gsap.context() 回傳的物件存進 context。後續就可以對這個範圍內的動畫進行操作
callback 一執行,gsap.utils.toArray(".day-06-sample") 會取得舞台裡的三個圖形,並依照 DOM 順序放進 samples。所以 samples[0]、samples[1]、samples[2],分別對應左到右的三個幾何樣本。
paused: true 讓動畫先停著,等使用者按下「同步開始」才播放repeat: -1 代表無限重複,如果是正整數就是直接控制重播次數yoyo: true 則讓每一輪來回播放defaults,不用每一段都重寫一次。stageRef 是傳給 gsap.context() 的 DOM 容器,用來限制 selector 的搜尋範圍;因此 Context 裡的 .day-06-sample 只會選到該容器內的圖形,不會影響頁面上其他同名元素。Timeline 與裡面的 Tween 都是在 callback 中建立,因此會被這個 context 一起管理。後面不必逐一找出三個 Tween,只要操作 context 就能處理整組動畫。接下來,我們來看看如果要利用 context 一次清除這些動畫,可以如何操作?

兩者都會清除 context 收集的動畫,差別在於 context.kill() 會終止動畫,但保留 GSAP 已經寫進元素的 inline styles ,位移、旋轉和顏色都還留著,只是動畫不會再繼續;context.revert() 同樣會終止動畫,但會移除動畫產生的 inline styles,讓元素回到執行動畫前的狀態
分別按下左右的按鈕,左側執行 context.kill(),右側執行 context.revert()。左側的三個圖形會停在操作當下;右側則直接回到 CSS 原本設定的位置與顏色。
但要注意的是,context 的用途是集中清理與限制 selector 範圍,不是拿來控制播放。一般的播放、暫停或倒轉,還是交給 Tween 和 Timeline。
在 Vue 元件卸載時,我通常會使用 context.revert() 進行清理,讓 GSAP 還原動畫造成的狀態變更,避免在較複雜的生命週期情境下,kill() 保留下來的狀態影響元件再次掛載時的動畫起點。
接著 GSAP 的最後,我想補充另一個實用的函式quickTo(),會建立可重複使用的 Tween 來快速更新單一屬性,特別適合滑鼠移動、游標追蹤等高頻事件。
舉例來說,若要讓物件持續跟隨游標移動,最暴力的做法就是反覆呼叫 gsap.to(),但每次事件觸發都會建立新的 Tween,造成不必要的負載;使用 quickTo() 則能先建立可重複使用的更新函式,事件觸發時只需傳入新值即可。
quickTo() 先替 x 和 y 各建立一個控制函式:
const xTo = gsap.quickTo(follower, "x", {
duration: 0.45,
ease: "power3.out",
});
const yTo = gsap.quickTo(follower, "y", {
duration: 0.45,
ease: "power3.out",
});
Pointer 移動時,把算好的座標交給它們:
function handlePointerMove(event) {
const { x, y } = getPosition(event);
xTo(x);
yTo(y);
}
它會重用同一個 Tween,把終點更新成最新座標,並依照設定的 duration 和 ease 追過去。
最直接的說,quickSetter() 沒有 Tween,同樣也是高頻更新,但物件隨時保持在 Pointer 位置上,沒辦法做延遲跟隨 (追趕) 的效果。範例如下:
const setX = gsap.quickSetter(follower, "x", "px");
const setY = gsap.quickSetter(follower, "y", "px");
function handlePointerMove(event) {
const { x, y } = getPosition(event);
setX(x);
setY(y);
}
第三個參數是單位。這裡傳入 "px",後續只需要提供數字,不會建立補間過程,收到新值就立刻寫入。從 demo 就可以明顯觀察到,quickTo() 看起來比較柔順,quickSetter() 則緊貼輸入位置。
貼心提醒!這類互動最好更新 x、y 等 transform 屬性,不要反覆修改 left 和 top。Demo 也會依舞台的實際尺寸限制座標,避免 Follower 跑出容器。
回顧一下今天的內容:gsap.context() 能幫我們收集動畫並限制 selector 的作用範圍;如果需要保留停止當下的畫面,可以用 context.kill(),而在元件卸載或需要恢復初始狀態時,則使用 context.revert()。
另外,針對 Pointer 這類高頻輸入,不需要一直重複建立 gsap.to()——需要補間動畫時選擇 quickTo(),只想即時寫入狀態就用 quickSetter()。 結合前幾天學到的技巧,就能做出非常多有趣的互動了!
GSAP 還有太多值得探索的玩法,以上是我認為非常關鍵的基礎,最核心的部分就先介紹到這邊。明天開始,我們將開始講解 Canvas!