本文緣起自筆者開發一個基於 PIXI.js 的線上動畫編輯器時,想系統學習 Canvas 相關知識,卻發現缺少合適的中文入門資料,於是萌生了撰寫這份“速通指北”的想法,歡迎感興趣的朋友訂閱我的 《Canvas 指北》專欄。
在這一章中,我們將一起探索 Canvas 是什麼,為什麼我們需要它,以及如何在 HTML 中使用 <canvas> 元素。如果你是 Web 圖形開發的新手,不用擔心——我們會從最基礎的概念開始,逐步帶你進入 Canvas 的精彩世界。
在 HTML5 出現之前,網頁上的圖形大多是靜態的——圖片、CSS 繪製的簡單形狀,或者是透過 Flash、SVG 等技術實現的動態效果。這些方法要麼功能有限,要麼需要額外的依賴,難以與現代 Web 應用的動態需求匹配。
Canvas(畫布)是 HTML5 引入的一個革命性特性。它是一個可以透過 JavaScript 腳本在網頁上繪製圖形的“畫板”。與傳統的 DOM 元素不同,Canvas 不保存繪製的圖形物件,主要透過繪圖指令即時渲染,必要時也可進行像素級處理,這使得它非常適合以下場景:
簡單來說,Canvas 讓你能用 JavaScript 在網頁上“畫畫”,而且畫出來的內容是動態的、可互動的,並且性能非常出色。它讓網頁從“展示信息”進化到了“創造視覺體驗”的新階段。
<canvas> 元素Canvas 的使用分為兩步:首先在 HTML 中放置一個 <canvas> 元素,然後透過 JavaScript 獲取它的繪圖上下文(context)並進行繪製。
<canvas>標籤<canvas> 是一個行內元素(inline),但通常我們把它當作區塊級元素來設定寬高,實踐中常設定 display: block;,可避免與文本基線對齊導致的底部空隙問題。它的基本語法如下:
<canvas id="myCanvas" width="400" height="300">
您的瀏覽器不支持 Canvas,請升級或更換瀏覽器。
</canvas>
注意:盡量不要用 CSS 的 width 和 height 來修改畫布的像素尺寸。如果透過 CSS 設定寬高,畫布會按原始像素被拉伸,導致圖形模糊。正確的做法是在
<canvas>標籤中直接指定 width 和 height 屬性,或者透過 JavaScript 動態設定。如果為了適配高 DPI 屏幕,通常會用 JavaScript 將 canvas 的像素尺寸設為 CSS 尺寸乘以
devicePixelRatio。
在 JavaScript 中,我們透過 <canvas> 元素的 getContext() 方法獲取繪圖上下文。上下文定義了所有繪圖方法和屬性。目前最常用的是 2D 上下文:
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
除了 '2d',還有 'webgl'(用於 3D 繪圖)和 'webgl2' 等上下文類型。在本教程中,我們將專注於 2D 上下文。
獲得了 ctx 物件後,我們就可以調用各種繪圖方法了。下面是一個簡單的例子:在畫布上繪製一個紅色的矩形。
<!doctype html>
<html>
<canvas id="myCanvas" width="200" height="100" style="border: 1px solid black">
</canvas>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'red';
ctx.fillRect(0, 0, 150, 75);
</script>
</html>
這段程式碼會在畫布的 (0,0) 位置開始,繪製一個寬 150 像素、高 75 像素的紅色矩形:

Canvas 的坐標系原點 (0,0) 位於畫布左上角,Y 軸正方向是向下的(與常見的數學坐標系 Y 軸向上相反)。也就是說,y 值越大,圖形的位置越靠下。
本章帶你快速掌握 Canvas 中最基礎的圖形——線條。我們將學習如何繪製直線,以及如何控制線條的粗細、端點樣式、連接樣式和虛線效果。
繪製線條需要掌握三個核心方法:
moveTo(x, y):將畫筆移動到指定坐標(不繪製任何東西)lineTo(x, y):從當前位置繪製一條直線到指定坐標(僅定義路徑,不實際描邊)stroke():對當前定義的路徑進行描邊此外,還需要透過 strokeStyle 設置線條顏色(預設黑色)。
畫一條從 (50,30) 到 (250,120) 的紅色直線:
<canvas id="lineCanvas" width="300" height="150"></canvas>
<script>
const canvas = document.getElementById('lineCanvas');
const ctx = canvas.getContext('2d');
ctx.strokeStyle = 'red'; // 設置線條顏色
ctx.moveTo(50, 30); // 起點 (50,30)
ctx.lineTo(250, 120); // 终点 (250,120)
ctx.stroke(); // 描邊繪製
</script>

注意: 以上程式碼未使用 beginPath(),僅適用於簡單場景。如果後續需要繪製多條獨立的線,必須調用 ctx.beginPath() 重置路徑,否則之前繪製的線會被重複描邊。我們將在第4章“路徑”中詳細介紹。
設置線條的粗細(像素)。預設值為 1.0。
線寬越大,線條越粗。效果對比:
<canvas id="widthCanvas" width="300" height="120"></canvas>
<script>
const canvas = document.getElementById('widthCanvas');
const ctx = canvas.getContext('2d');
// 線寬 1
ctx.beginPath();
ctx.lineWidth = 1;
ctx.moveTo(20, 20);
ctx.lineTo(280, 20);
ctx.stroke();
// 線寬 5
ctx.beginPath();
ctx.lineWidth = 5;
ctx.moveTo(20, 50);
ctx.lineTo(280, 50);
ctx.stroke();
// 線寬 10
ctx.beginPath();
ctx.lineWidth = 10;
ctx.moveTo(20, 80);
ctx.lineTo(280, 80);
ctx.stroke();
</script>

"butt"(預設):平直端點,不超出線段端點"round":圓形端點,半圓直徑等於線寬"square":方形端點,超出線段端點一半線寬畫三條相同起止點的線段,分別設置不同 lineCap。效果對比:
<canvas id="capCanvas" width="320" height="120"></canvas>
<script>
const canvas = document.getElementById('capCanvas');
const ctx = canvas.getContext('2d');
ctx.lineWidth = 10;
ctx.strokeStyle = 'blue';
// butt (預設)
ctx.beginPath();
ctx.lineCap = 'butt';
ctx.moveTo(40, 30);
ctx.lineTo(140, 30);
ctx.stroke();
// round
ctx.beginPath();
ctx.lineCap = 'round';
ctx.moveTo(40, 60);
ctx.lineTo(140, 60);
ctx.stroke();
// square
ctx.beginPath();
ctx.lineCap = 'square';
ctx.moveTo(40, 90);
ctx.lineTo(140, 90);
ctx.stroke();
</script>

定義兩條線相交處的拐角形狀。可選值:
"miter"(預設):尖角(透過 miterLimit 限制尖銳程度)"round":圓角"bevel":平角(切去尖角)繪製兩條折線,分別應用不同連接樣式。效果對比:
<canvas id="joinCanvas" width="320" height="200"></canvas>
<script>
const canvas = document.getElementById('joinCanvas');
const ctx = canvas.getContext('2d');
ctx.lineWidth = 15;
// miter (預設)
ctx.beginPath();
ctx.lineJoin = 'miter';
ctx.moveTo(30, 30);
ctx.lineTo(80, 80);
ctx.lineTo(130, 30);
ctx.stroke();
// round
ctx.beginPath();
ctx.lineJoin = 'round';
ctx.moveTo(160, 30);
ctx.lineTo(210, 80);
ctx.lineTo(260, 30);
ctx.stroke();
// bevel
ctx.beginPath();
ctx.lineJoin = 'bevel';
ctx.moveTo(30, 100);
ctx.lineTo(80, 150);
ctx.lineTo(130, 100);
ctx.stroke();
</script>

當 lineJoin = "miter" 時,該屬性限制尖角的長度。尖角長度是指內角頂點到外角頂點的距離。如果尖角長度超過 miterLimit 與線寬的乘積,則連接樣式會回退為 bevel。預設值為 10.0。
保持同一折線,用不同 miterLimit 值觀察尖角變化。效果對比:
<canvas id="miterCanvas" width="320" height="200"></canvas>
<script>
const canvas = document.getElementById('miterCanvas');
const ctx = canvas.getContext('2d');
ctx.lineWidth = 10;
ctx.strokeStyle = 'green';
ctx.lineJoin = 'miter'; // 必須為 miter 才生效
// miterLimit = 1(較小,尖角被截斷為平角)
ctx.beginPath();
ctx.miterLimit = 1;
ctx.moveTo(30, 30);
ctx.lineTo(80, 90);
ctx.lineTo(130, 30);
ctx.stroke();
// miterLimit = 5(中等,仍為尖角)
ctx.beginPath();
ctx.miterLimit = 5;
ctx.moveTo(160, 30);
ctx.lineTo(210, 90);
ctx.lineTo(260, 30);
ctx.stroke();
// miterLimit = 10(預設,通常保持尖角)
ctx.beginPath();
ctx.miterLimit = 10;
ctx.moveTo(30, 100);
ctx.lineTo(80, 160);
ctx.lineTo(130, 100);
ctx.stroke();
</script>

透過數組指定虛線的線段與間隙長度(像素)。數組元素依次為線段長、間隙長、線段長、間隙長……重複循環,若要恢復實線,可傳入空數組 ctx.setLineDash([])。
繪製多條虛線,展示不同數組模式。效果對比:
<canvas id="dashCanvas" width="320" height="140"></canvas>
<script>
const canvas = document.getElementById('dashCanvas');
const ctx = canvas.getContext('2d');
ctx.lineWidth = 3;
// 實線
ctx.beginPath();
ctx.setLineDash([]);
ctx.moveTo(20, 20);
ctx.lineTo(300, 20);
ctx.stroke();
// 虛線 [10, 5] (10畫,5空)
ctx.beginPath();
ctx.setLineDash([10, 5]);
ctx.moveTo(20, 50);
ctx.lineTo(300, 50);
ctx.stroke();
// 點線 [2, 4] (2畫,4空)
ctx.beginPath();
ctx.setLineDash([2, 4]);
ctx.moveTo(20, 80);
ctx.lineTo(300, 80);
ctx.stroke();
// 點劃線 [15, 5, 2, 5] (長畫,短空,點,短空)
ctx.beginPath();
ctx.setLineDash([15, 5, 2, 5]);
ctx.moveTo(20, 110);
ctx.lineTo(300, 110);
ctx.stroke();
</script>

lineDashOffset 屬性用於設置虛線模式的起始偏移量(像素),會改變虛線圖案在路徑上的起點位置。連續更新該值(遞增或遞減)可以實現“流動虛線”動畫效果。
效果對比:繪製三條相同的虛線,分別設置不同偏移量。
<canvas id="dashOffsetCanvas" width="320" height="120"></canvas>
<script>
const canvas = document.getElementById('dashOffsetCanvas');
const ctx = canvas.getContext('2d');
ctx.lineWidth = 4;
ctx.strokeStyle = 'purple';
ctx.setLineDash([10, 5]); // 統一虛線模式
// 偏移 0 (預設)
ctx.beginPath();
ctx.lineDashOffset = 0;
ctx.moveTo(20, 20);
ctx.lineTo(300, 20);
ctx.stroke();
// 偏移 5
ctx.beginPath();
ctx.lineDashOffset = 5;
ctx.moveTo(20, 50);
ctx.lineTo(300, 50);
ctx.stroke();
// 偏移 -8
ctx.beginPath();
ctx.lineDashOffset = -8;
ctx.moveTo(20, 80);
ctx.lineTo(300, 80);
ctx.stroke();
</script>

在下一篇中,你將學到:
beginPath、closePath 的正確姿勢;nonzero 和 evenodd 兩種填充規則的原理與效果;arc 和 arcTo 的詳細對比與適用場景。掌握了這些,你就能組合路徑創造出複雜圖形,為後續的樣式與動畫打下堅實基礎。