把網頁當畫布揮灑的 Canvas 02:動畫原理

動畫是如何動起來的呢?


動畫原理

凡事先 Google ,不懂問 Wiki !

動畫是指由許多影格靜止的畫面,以一定的速度(如每秒16張)連續播放時,肉眼因視覺殘象產生錯覺,而誤以為畫面活動的作品。(維基百科

簡單來說,動畫,就是一連串流暢畫面的集合,在短時間內快速輪播畫面,透過人體的視覺暫留特徵,達到畫面動起來的效果。我們可以把動畫中的每一個畫面稱為 「影格」(Frame),或叫作 「幀」(ㄓㄥˋ)。通常我們會用 「影格率」 作為測量顯示影格的量度單位,指的是「每秒顯示影格數」(Frame per Second,FPS),用以描述每秒播放多少影格。

動畫的影格概念

在前端開發的技術中,我們可以透過 CSS 來設定網頁元素的樣式,包括大小、顏色、定位位置、旋轉角度等等,而 @keyframesanimation 的實作讓我們得以製作簡單、有規律的網頁動畫。但,如果要製作更精緻、具備物理效果或是可以和使用這互動的動畫時,CSS 的 animation 便顯得不足。

所以,我們可以利用 JavaScript 來操作網頁元素的 CSS 屬性,或是透過 Canvas 元素來繪製高效而精美的互動設計。配合函式模組化、物件導向開發以及諸多重力、遊戲引擎的協助,讓我們的網頁互動擁有更多可能。


透過計時函式繪製動畫

方才提到,所謂動畫是由多個影格在短時間內快速輪播而成。在 JavaScript 中,我們可以透過計時函式 setTimeout()setInterval()requestAnimationFrame() 來不斷更新網頁中的元素狀態,達到動畫的效果。

setTimeout()

setTimeout() 是倒數計時函式,屬於全域物件 window 的方法,在倒數完畢後,會執行所傳入的回呼函式。常見於遊戲開始前的倒數動畫等等。

setInterval()

setInterval() 是間隔計時函式,屬於全域物件 window 的方法,每隔一段時間,會執行所傳入的回呼函式。譬如製作進度條,或是更新遊戲畫面等等,

requestAnimationFrame()

requestAnimationFrame() 是更新動畫函式,屬於全域物件 window 的方法。與 setTimeout()setInterval() 的差異在於,它會優化動畫函式的執行速率與瀏覽器本身的 FPS 更新頻率相同,降低不必要的耗能與資源,〈如何提升動畫效能?〉這篇文章歸納三者之間的詳細比較。簡而言之,在實作中比較推薦使用 requestAnimationFrame() 更新影格畫面


透過 Canvas 簡單繪製動畫

本篇實作範例中,我們宣告一個變數 time 用來儲存時間,並透過 setInterval() 來更新影格。

每次更新影格都呼叫一個函式,先利用 ctx.clearRect() 清出先前的繪圖,避免重複繪製而留下殘影。接著,重新渲染當前網頁元素的狀態,包括橋墩上的電車以及隨著滑鼠伸縮、變色的旗幟,達到動畫的效果。

初始化 Canvas 以及相關變數:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Init canvas
const canvas = document.getElementById('my-canvas')
const ctx = canvas.getContext('2d')

const canvas_size = 500
canvas.width = canvas_size
canvas.height = canvas_size

let time = 0

function draw() {
time++

// Clear the canvas(每次更新穎格)
ctx.clearRect(0, 0, canvas_size, canvas_size)

// ...
}

// update the canvas every 1000 / 30 second
setInterval(draw, 1000 / 30)

// Mousemove Event
const mouse = {x: 0, y:0}
canvas.addEventListener('mousemove', function(e) {
mouse.x = e.offsetX
mouse.y = e.offsetY
})

繪製電車:

1
2
3
4
5
6
7
8
9
10
11
12
// Train
let carX = time % 550 * 2 - 50
ctx.lineWidth = 1
ctx.fillStyle = 'skyblue'
ctx.beginPath()
ctx.fillRect(carX, 265, 50, 30)
ctx.strokeRect(carX, 265, 50, 30)
ctx.arc(carX + 10, 295, 5, 0, Math.PI)
ctx.arc(carX + 40, 295, 5, 0, Math.PI)
ctx.fillStyle = 'black';
ctx.fill();
ctx.stroke();

繪製旗幟:

1
2
3
4
5
6
7
8
9
10
11
12
// Flag
ctx.beginPath()
ctx.moveTo(225, 200)
// change the position of flag when mouse moving
ctx.lineTo(225, 50 + mouse.y / 5)
ctx.lineTo(250, 60 + mouse.y / 5 - time % 5)
ctx.lineTo(225, 70 + mouse.y / 5)
ctx.closePath()
// change the color of flag when mosue moving
ctx.fillStyle = `hsl(${mouse.x % 360}, 50%, 50%`
ctx.fill()
ctx.stroke()

⭐ 善用 % 取餘數的特性,獲得 0 ~ X 之間的數,可以創造重複的動畫效果(旗幟被風吹動、電車反覆行駛)。


參考資料

  1. 動畫互動網頁特效入門(JS/CANVAS):5-1 Canvas 繪圖基礎語法與動畫原理
  2. MDN:Canvas 教學文件
  3. HTML5 Canvas Tutorials Home
把網頁當畫布揮灑的 Canvas 03:操作座標系 把網頁當畫布揮灑的 Canvas 01:基礎圖形繪製

評論

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×