iT邦幫忙

2026 iThome 鐵人賽

DAY 1
1
Modern Web

前端互動視覺技術實作 LAB系列 第 1

Day 1|動畫不會 DOM?Vue 生命週期與掛載

  • 分享至 

  • xImage
  •  

嗨嗨大家好,我是一名剛轉職前端的小菜雞工程師。
之前在參加前端轉職班的期間,我不知不覺在社群上收藏了不少技術分享。可能是因為過去的設計背景,我存下來的內容大多和畫面、動畫、互動有關。每次看到那些很酷的效果,都會忍不住想:「這到底是怎麼做出來的?」

培訓結束後,總算可以認真挖出這些收藏,一個個研究、實際動手試試看。這個系列會記錄我摸索各種技術與套件的過程,也會比較同一個效果換不同工具來做,寫法和結果有什麼差別。
最後,希望能把這段時間學到的新技術用在個人作品集網站上,做出許多炫砲又有趣的視覺互動!

動畫開始之前...

在開始製作網頁動畫之前,一定要先了解動畫是怎麼在瀏覽器中運作,而這就離不開 DOM 與生命週期的概念。

不論是使用原生 JavaScript,還是 React、Vue 等前端框架,都有各自的生命週期概念。原生 JavaScript 主要是配合頁面與 DOM 的載入流程,而 React、Vue 則將生命週期延伸到元件(Component)層級,讓我們可以在元件建立、更新或銷毀的不同階段執行程式。

理解生命週期最大的好處,就是知道動畫該在什麼時機執行,也能避開因為生命週期鉤子使用錯誤而踩到的各種坑,讓動畫效果更符合預期。

本系列將以 Vue 作為主要開發框架,生命週期鉤子的相關概念可參考 Vue 的官方文件。

接下來我會以三個 demo 來實際演示動畫掛在不同鉤子上的差異
(建議搭配 demo網頁 操作,會比較好理解喔~~)
Day1 demo網頁連結 => https://mountainway54.github.io/30day-interactive-website-lab/#/day-01
https://ithelp.ithome.com.tw/upload/images/20260820/20183149MBOpdJAYcT.png

Demo 1:是 Callback 來得太早

寫好動畫後,並不是直接呼叫就能執行,而是必須等到 Vue 將元件渲染到 DOM 後,才有辦法進行操作。

首先,我們先建一個簡單的方塊:

<template>
  <div ref="box" class="demoBox"></div>
</template>

再來我們寫一個簡單的「向右移動」動畫,moveBox()會先算出方塊能移動的範圍,每次往右移動 2px,碰到右側後,就回到左邊重新開始:

function moveBox() {
  // 取得要移動的元素
  const el = box.value;

  // 計算可移動的最大 X 座標
  const maxX = Math.max(el.parentElement.clientWidth - el.offsetWidth, 0);

  // 每次向右移動 2px,到達最右邊後回到起點
  xPosition = xPosition >= maxX ? 0 : xPosition + 2;

  // 使用 transform 移動元素
  el.style.transform = `translate3d(${xPosition}px, 0, 0)`;

  // 在瀏覽器下一幀繼續執行,形成連續動畫
  frameId = requestAnimationFrame(moveBox);
}

上方範例直接呼叫 moveBox()。這時程式還處於 setup 階段,Vue 元件的 DOM 尚未掛載完成,因此無法取得方塊元素。如果沒有先判斷元素是否存在,程式就會報錯:

try {
  moveBox();
} catch (error) {
  message.value = error instanceof Error ? error.message : String(error);
}

下方範例把 moveBox() 放進 onMounted()。等元件掛載完成後,程式就能取得方塊,動畫也能順利播放:

onMounted(() => {
  moveBox();
});

Demo 2:元件離去,動畫仍在默默執行

動畫需要等到 DOM 完成渲染才能開始執行,但元件卸載後,動畫並不會自動停止。
在這個 demo 中,按下掛載方塊後,兩個方塊都會開始移動;按下移除方塊後,它們也會一起從畫面上消失。
不同的是,下方方塊雖然已經從畫面移除,動畫仍持續在背景執行。
這樣會造成什麼問題呢?

兩個方塊都會在 onMounted() 執行後呼叫 startAnimation()去播放 moveBox()動畫:

onMounted(() => {
  state.xPosition = 0;
  startAnimation();
});

function startAnimation() {
  isRunning.value = true;
  frameId = requestAnimationFrame(moveBox);
}

按下「移除方塊」後,兩個方塊都會從畫面卸載。不過,DOM 被移除不代表先前預約的動畫 callback 會自動停止。

上方範例會在 onUnmounted() 呼叫 stopAnimation(),再使用 cancelAnimationFrame() 取消下一次動畫:

function stopAnimation() {
  cancelAnimationFrame(frameId);
  isRunning.value = false;
}

onUnmounted(() => {
  stopAnimation();
});

下方範例則故意不呼叫 stopAnimation()。雖然方塊已經看不見,moveBox() 還是會繼續執行下一幀:

onUnmounted(() => {
  // 沒有呼叫 stopAnimation()
  emit("trace", "沒有取消動畫,callback 會繼續執行");
});

可以觀察到,如果動畫沒有被正常取消,即使元件已經卸載,動畫仍可能在背景持續執行

當方塊再次掛載時,新舊動畫就會同時執行並一起修改位置
原本每次只移動 2px,疊加後可能變成 4px、6px...,因此方塊會愈跑愈快

不過這個 demo 是為了讓問題更明顯,刻意讓舊的 callback 在方塊重新掛載後,繼續控制新建立的元素。實際開發時,未清理的 callback 不一定會出現完全相同的效果,但它仍可能在背景持續執行,造成額外的效能負擔。

Demo 3:終點變了,你卻還停留在原地

一般來說,當資料或狀態改變時,動畫也需要配合更新後的畫面重新調整。
但如果沒有在onUpdated()中重新執行動畫,即使終點位置已經改變,方塊仍然會停在原本的位置。為什麼會有這個差別?

首先在onMounted,讓上下的方塊都移動到初始設定的終點,也就是中間位置:

onMounted(() => {
  moveToEnd(topBox.value, topEnd.value, "top");
  moveToEnd(bottomBox.value, bottomEnd.value, "bottom");
});

按下按鈕 改變終點位置後,原本設定的終點位置會從中間改至最右側。

onMounted() 只會在元件第一次出現時執行,不會因為終點移動就再跑一次。
所以上面的方塊根本不知道終點變了,還是停在原本的位置;
而下方的方塊額外在 onUpdated() 再次執行動畫。當終點位置更新以後,它會繼續移動到新的位置:

onUpdated(() => {
  if (!shouldMove) return;

  shouldMove = false;
  moveToEnd(bottomBox.value, bottomEnd.value, "bottom");
});

總而言之,這個 demo 表達的是,動畫的目標位置可能會跟著畫面改變。第一次進入頁面時,可以在 onMounted 取得位置;畫面更新後,則要用 onUpdatednextTick() 等畫面更新完成,再讀取一次。

小結

生命週期還有許多其他的動畫應用。例如,元件的淡入淡出會搭配 <Transition> 的 enter、leave 等 Hook;頁面切換動畫通常會在 onMounted() 建立,並於 onUnmounted() 清理;需要等待畫面更新後才能執行的動畫,則會搭配 nextTick()。把這些瀏覽器運作的流程釐清,後面不管使用 CSS、GSAP、Canvas 或其他動畫工具,都比較不容易被奇怪的行為卡住。

那 Vue 的生命週期就先複習到這邊,明天馬上就要來開始講解動畫啦! ~敬請期待~


下一篇
Day 2|GSAP 入門(1):認識基礎參數
系列文
前端互動視覺技術實作 LAB2
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

1
饅頭
iT邦新手 5 級 ‧ 2026-08-20 20:38:50

太讚了!居然是寫動畫的!很需要!(,,・ω・,,)

哈哈太好了~
如果文章或 demo 有不清楚的部分
請再給我回饋,交流一下~~ /images/emoticon/emoticon33.gif

饅頭 iT邦新手 5 級 ‧ 2026-08-21 12:54:17 檢舉

好的!我覺得滿詳細的!!

我要留言

立即登入留言