網頁上的動態圖表、2D 遊戲畫面、粒子動畫和簽名板,很多都可以透過 HTML5 的 <canvas> 來實現。這篇文章會從 Canvas 2D 繪圖的基本概念開始,帶你一步步畫出第一個 2D 圖形。
<canvas> 是 HTML5 新增的一塊繪圖畫布,我們可以用 JS 在上面畫圖形、文字、圖片,甚至做動畫。
你可能會想這些東西用 HTML、CSS 不是也做得到嗎?
這樣說也沒錯啦XD,但差別在於,傳統HTML+CSS 通常是建立一個個 DOM 元素,再去調整它們的樣式和位置;<canvas> 則是一張由像素組成的點陣圖(bitmap),比較像真的拿了一張畫布,直接用 JavaScript 告訴它「在哪個座標、畫什麼東西」。所以當畫面上有很多東西要一直移動、變化或重新繪製時,就很適合使用 Canvas。
我們先在檔案中設定畫布與畫筆,先在 HTML 放入 <canvas>:
<canvas id="myCanvas" width="400" height="300"></canvas>
<script setup>
const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
</script>
#myCanvas 就是我們的畫布,可以用 width 和 height 來設定尺寸;接著用 JavaScript 找到元素,再取得 2D 繪圖環境(context),之後的繪圖指令都會透過 ctx 執行,你可以把ctx想成畫筆。
座標系統的部分,只需要注意,Canvas 的座標原點 (0, 0) 位於左上角,x 軸數值越大會越往右,而 y 軸數值越大則會越往下。這與一般數學座標系統的 y 軸方向相反,我都是直接記得,原點是在左上,這樣就不會搞混了~~
Canvas 有三個常用的矩形方法:
ctx.fillRect(50, 50, 100, 80); // 畫填色矩形
ctx.strokeRect(200, 50, 100, 80); // 畫矩形邊框
ctx.clearRect(0, 0, 50, 50); // 清除一塊區域,恢復透明,橡皮擦的概念
它們的參數都是 (x, y, 寬, 高),其中 x、y 是矩形左上角的座標。
ctx.beginPath(); // 開始新路徑
ctx.moveTo(50, 200); // 移到起點,不畫線
ctx.lineTo(150, 250); // 從目前位置畫到這個點
ctx.lineTo(100, 300); // 再畫到下一個點
ctx.closePath(); // 連回起點,形成封閉圖形
ctx.stroke(); // 畫出邊框
流程很簡單:beginPath() 開始路徑 → moveTo() 設定起點 → lineTo() 畫線 → closePath() 連回起點 → 最後用 stroke() 畫出邊框。如果想填滿圖形,則可以改用 fill()。
每次畫新圖形前,記得呼叫 beginPath()。少了這一步,新路徑可能會接到上一條路徑,出現意料之外的線條。
圓形和弧線都使用 arc():
ctx.beginPath();
ctx.arc(200, 150, 50, 0, Math.PI * 2); // 畫完整的圓
ctx.fill();
arc() 的參數是 (x, y, 半徑, 起始角度, 結束角度)。x、y 是圓心座標,角度使用弧度(radian)。完整一圈是 Math.PI * 2,也就是 360 度。若要畫半圓,可以把角度設為 0 到 Math.PI。
畫圖前可以先像這樣,設定填色、邊框顏色和線條粗細:
ctx.fillStyle = "#ff6b6b"; // 填色
ctx.strokeStyle = "#4834d4"; // 邊框顏色
ctx.lineWidth = 4; // 線條粗細
樣式要在繪圖指令之前設定,才會套用到接下來的圖形。fillStyle 和 strokeStyle 可以使用色碼、rgb()、rgba() 或顏色名稱,例如 "tomato"。
把前面的技巧組合起來,我們可以用簡單的幾何圖形,畫一張簡單的圖,這邊我就先示範畫卡比獸(五官簡單又可愛 XD)請看 demo ⇒ Day7 demo
function drawSnorlax(step = 4) {
ctx.clearRect(0, 0, width, height);
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.lineWidth = 7;
ctx.strokeStyle = "#17212b";
// 畫藍綠色橢圓頭部
if (step >= 1) {
ctx.fillStyle = "#365f64";
ctx.beginPath();
ctx.ellipse(200, 160, 120, 108, 0, 0, Math.PI * 2);
ctx.fill();
}
// 畫左右三角形耳朵
if (step >= 2) {
ctx.fillStyle = "#365f64";
ctx.beginPath();
ctx.moveTo(96, 108);
ctx.lineTo(118, 48);
ctx.lineTo(154, 76);
ctx.closePath();
ctx.fill();
ctx.beginPath();
ctx.moveTo(246, 76);
ctx.lineTo(282, 48);
ctx.lineTo(304, 108);
ctx.closePath();
ctx.fill();
}
// 畫奶油色的橢圓臉部
if (step >= 3) {
ctx.fillStyle = "#f1dfb6";
ctx.beginPath();
ctx.ellipse(200, 178, 100, 78, 0, 0, Math.PI * 2);
ctx.fill();
// 畫倒三角形美人尖
ctx.fillStyle = "#365f64";
ctx.beginPath();
ctx.moveTo(140, 94);
ctx.lineTo(260, 94);
ctx.lineTo(200, 134);
ctx.closePath();
ctx.fill();
}
// 畫瞇瞇眼
if (step >= 4) {
ctx.beginPath();
ctx.moveTo(142, 162);
ctx.lineTo(172, 162);
ctx.moveTo(228, 162);
ctx.lineTo(258, 162);
ctx.stroke();
// 畫水平線嘴巴
ctx.beginPath();
ctx.moveTo(170, 196);
ctx.lineTo(230, 196);
ctx.stroke();
// 畫左右兩顆白色三角形牙齒
ctx.fillStyle = "#ffffff";
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(166, 196);
ctx.lineTo(182, 196);
ctx.lineTo(174, 186);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(218, 196);
ctx.lineTo(234, 196);
ctx.lineTo(226, 186);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.lineWidth = 7;
}
}
這篇文章介紹了幾個 Canvas 基礎,包含基本的 context、fillRect()、arc()、fillStyle、strokeStyle 和 lineWidth,也透過基本的圖形畫了一個簡單的小圖,明天我們會將 Canvas 搭配 GSAP 來製作會動的卡比獸^^