圖形與動畫(Canvas 與他的朋友們)
核心問題
以前的網頁只能展示乾巴巴的文字和圖片。但如果你想做打磚塊遊戲、華麗的動態特效、或是可以自由拖曳的資料報表,僅僅靠 <div> 是遠遠不夠的。這就是 Canvas(畫布) 誕生的原因。
本指南將帶你從畫下第一條線開始,一路打怪升級,最終親手寫出能在瀏覽器中流暢執行 60 幀的粒子引擎。
1. 什麼是 Canvas?
如果說早期的網頁是用樂高積木(HTML 標籤)拼湊起來的靜態模型,那麼 HTML5 的 <canvas> 標籤就是扔給你一張巨大的數位白紙,然後遞給你一支靠程式碼控制的畫筆,剩下的全交給你自由發揮。
這裡面的畫沒有任何標籤結構。你用畫筆塗上去的心血,一旦落筆就變成了最純粹的「像素顏料」。
1.1 Canvas vs SVG:兩種不同流派的藝術家
在前端畫圖界,Canvas 有個宿敵叫 SVG。它們代表了兩種截然不同的繪畫觀念:
- Canvas(點陣圖畫板):
- 原理:就像真實在紙上塗色,幾筆畫上去就變成一團顏料(像素點)。
- 優勢:電腦只管往螢幕上「灑顏料」,效能起飛!能同時畫出大幾千個活蹦亂跳的閃爍粒子。
- 缺點:畫完就沒法單獨反悔(沒法透過 DOM 節點選擇),且放大會造成馬賽克發虛。
- SVG(向量圖拼接):
- 原理:就像做 PPT。你畫一個圓,它就生成一個獨立標籤的「圓實體」放在畫面上。
- 優勢:不管放大 100 倍還是 10 萬倍,永遠極為清晰。每個形狀都是獨立的 DOM 節點,你可以隨時用 CSS 和 JS 改變它的顏色或綁定點擊事件。
- 缺點:如果你試圖放幾萬個物件亂飛,繁重的 DOM 樹和排版引擎會直接把瀏覽器卡死。
簡單總結:玩動態遊戲、做酷炫粒子特效用 Canvas;畫精密的 Logo、寫互動清晰的小圖表用 SVG。
2. 第一筆:理解反直覺的座標系
2.1 這張紙的上下怎麼顛倒了?
當你準備下筆時,得先明白 Canvas 裡的尺子是反著的。對於傳統的數學課座標系,中心點零點在中間,越往上越大。但在電腦螢幕顯示領域,幾乎所有裝置的「原點(0,0)」都定在螢幕的最左上角。向右走 X 軸變大沒問題,但是向下走,Y 軸變大。
Canvas 座標系統的核心原則:
- 原生單位: 像素 (px),與螢幕物理像素 1:1 對應。
- X 軸: 向右為正方向,從
0到canvas.width。 - Y 軸: 向下為正方向,從
0到canvas.height。
👇 拖曳下面的小圓點,直觀感受電腦圖形學中的座標原點與走向:
2.2 給你的魔法畫筆上調料
有了座標體系,我們就能召喚畫筆了(程式碼中稱為 Context,或縮寫 ctx)。就如同拿著真實的調色盤作畫,Canvas 的 API 設計完美遵循了物理作畫的三個步驟:
- 調色(State):透過
fillStyle設定填充色,strokeStyle設定描邊色。 - 構形(Path):構思你是要畫一條線(
lineTo)、還是一個圓(arc)、亦或一個矩形(rect)。 - 極簡下筆(Render):決定是內部填充(
fill())還是勾勒邊緣(stroke())。
由於 Canvas 是純粹的點陣圖畫布,「落子無悔」,你一旦畫下,它立刻乾涸成為像素,無法再被撤銷為獨立物件。
👇 嘗試在下面的演示中挑選不同形狀和顏色,看看背後的程式碼是如何執行上述「三步走」的:
3. 翻頁動畫書:如何讓畫面動起來極度絲滑
既然 Canvas 一旦填色就變成了永久的像素,那麼各種 HTML5 頁遊裡滿屏亂跑的角色是怎麼做出來的?
答案是「騙過你的眼睛」。這和手翻動畫書或者電影膠片的原理一模一樣。
- 擦黑板(Clear): 用
clearRect()把整塊畫布上的內容毫不留情地清空。 - 計算新位置(Update): 讓角色的 X 座標往前偷偷加 2 個像素點。
- 下筆重畫(Render): 把角色在新的位置重新畫一次。
- 瘋狂迴圈(Loop): 結合瀏覽器內建的極為精準的節拍器
requestAnimationFrame。它會以顯示器的重新整理率(通常是每秒 60 次,即 60 FPS)重複這三個動作。
由於人眼自帶「視覺殘留」,在每秒 60 次的【擦除 -> 更新 -> 重繪】中,你看到的不僅不是閃爍的黑板,反而是如同絲綢般順滑的動畫。
👇 在下方的演示中調整播放速度,觀察每一幀的位移是如何連綴成流暢運動的:
4. 瞎子摸象:在 Canvas 裡面怎麼做點擊互動?
因為 Canvas 畫布在瀏覽器眼裡只是一張沒有任何結構的「顏料布」。假設你在畫布上用 arc() 畫了一隻怪獸,當你想要實現「點擊怪獸扣血」時,你根本沒法使用傳統的 document.getElementById 來取得這個怪獸。因為在 HTML 結構中,只有那個寬 600 像素的死板 <canvas> 標籤。
這就是圖形程式設計中最經典的問題:碰撞檢測 (Collision Detection) 與事件代理。
由於瀏覽器只知道你的滑鼠點擊了 Canvas 的螢幕座標 (x, y),你需要自己去透過國中的幾何數學進行反算:
- 對於圓形: 透過勾股定理計算
滑鼠點擊處到圓心位置的距離,如果距離小於半徑,則說明「被點中了」。 - 對於矩形: 判斷點擊的
x是否在矩形的左右邊界內,同時y是否在上下邊界內。
無論你的畫布上有多少元素,滑鼠懸停或點擊事件永遠是綁定在 Canvas 這個唯一容器上的,這就是終極的「事件委託」。
👇 試著在下面使用滑鼠(點擊、拖曳、懸停)或鍵盤(方向鍵移動),體會這種「手動算距離」的底層互動邏輯:
Instructions / 操作说明
- Click Mode:点击画布创建圆形,按住 Shift 可创建不同颜色
Event Log / 事件日志
5. 解放算力:粒子系統與視覺魔法
到了這一步,當我們把「座標系」、「動畫迴圈」以及「顏色與形狀」全部融合,並將其數量暴增到成百上千個微小碎片時,你就掌握了引爆視覺的終極殺器:粒子系統(Particle System)。
其核心思路極為粗暴且有效:
- 建立一個巨大的陣列,裡面塞滿了幾百個獨立的「粒子物件」。
- 每個物件擁有自己的獨立生命週期(
life)、加速度(vx/vy)、重力阻尼(gravity)。 - 每次
requestAnimationFrame觸發時,遍歷更新這幾百個粒子,然後渲染,最後悄悄清理掉那些「死亡」(生命值耗盡/掉出螢幕)的粒子。
你的瀏覽器一瞬間就能變成一台製造煙花、大雪和爆炸的夢工廠。
👇 點擊不同的效果,調整重力與粒子數,觀察它們是如何透過最簡單的物理數學公式呈現出複雜的群體視覺:
6. 守護 FPS 榮耀:如何應對高燒的 CPU?
讓成千上萬個物件在一秒內計算並重畫 60 遍是非常消耗效能的。如果毫無章法,你的電腦風扇很快就會起飛。
以下是真正引擎大佬用來搶救幀率的「護體絕技」:
局部擦黑板(髒矩形 Dirty Rect): 一個角色在寬廣的草原上奔跑,你千萬不要每幀去
clearRect整片大草原!角色經過哪一小塊,你就用「小板擦」擦掉那一塊並覆蓋重繪,效能立刻飆升指數倍。後台替身魔法(離屏 Canvas): 如果背景是繁星漫天、有著各種複雜絢麗的山脈,每次都即時渲染太蠢了。我們通常在記憶體裡偷偷建一個看不見的
<canvas>,把它精美地畫上去一次。之後的每一幀重新整理中,只需要透過drawImage()將這張合成好的「靜態底片」直接貼出,免去了海量的基礎計算。批量洗畫筆(Batching): 調色盤裡從紅色換到藍色,在底層是昂貴的。如果畫布上有 1000 個紅色圓和 1000 個藍色圓交叉散落。最快的方法是:先把紅顏料準備好,遍歷畫完所有紅圈,再換藍顏料畫所有藍圈。這是著名的批量渲染(Batch Rendering)思想。
👇 將物件數量拉到 3000 以上,看著網頁掉進卡頓的深淵,再依次開啟右下方的「最佳化技術」開關,親眼見證實打實的幀率搶救:
7. 專業名詞總結
| 術語 | 通俗解釋 |
|---|---|
| Canvas | HTML5 提供的 2D 畫布。繪製極快,但畫完就變成顏料像素,不支援透過 DOM 操作內容。 |
| SVG | 向量圖。放大永遠不模糊,且每個圖形都是獨立的標籤元素,可以輕易綁定各種 CSS 樣式和互動。 |
| Context (ctx) | 你申請到的那支「2D 魔法畫筆」,用來調色、設定形狀和繪製各種特殊效果。 |
| requestAnimationFrame | 瀏覽器內建的神級節拍器,會嚴格依照顯示器的重新整理率執行回呼,是製作絲滑動畫的不二之選。 |
| FPS (Frame Rate) | 幀率。60 FPS 代表一秒內瀏覽器幫你無縫擦除了 60 次畫布並重畫了 60 幅新圖。 |
| 髒矩形 (Dirty Rect) | 只在發生變化的那一點微小區域內進行精準擦除和重繪,從而強力保留效能。 |
| 離屏 Canvas | 藏在記憶體裡的「影子畫布」。把極度複雜但不會動的景物提前畫好,以後就當死貼圖拿來重複使用。 |
從一條簡單的直線段,到宏大絢麗的粒子系統引擎;一切看似魔法的特效,不過是每秒 60 次的座標計算與重繪輪迴罷了。