使用 canvas 繪製形狀
既然我們已經設定好 canvas 環境,我們就可以深入瞭解如何在 canvas 上繪圖了。透過本文,你將學會如何繪製矩形、三角形、線條、弧線和曲線,熟悉一些基本圖形。在使用 canvas 繪製物件時,路徑是必不可少的,我們將瞭解如何完成這項工作。
網格
在我們開始繪圖之前,我們需要討論 canvas 網格或座標空間。上一頁的 HTML 骨架中有一個 canvas 元素,寬 150 畫素,高 150 畫素。

通常,網格中的 1 個單位對應於 canvas 上的 1 個畫素。此網格的原點位於左上角,座標為 (0,0)。所有元素都相對於此原點放置。因此,藍色方塊左上角的座標為 (x,y),即距離左側 x 畫素,距離頂部 y 畫素。在本教程的後面,我們將看到如何將原點平移到不同的位置,旋轉網格,甚至縮放它,但現在我們將堅持使用預設設定。
繪製矩形
與 SVG 不同,<canvas> 只支援兩種基本形狀:矩形和路徑(由線段連線的點列表)。所有其他形狀都必須透過組合一個或多個路徑來建立。幸運的是,我們有各種路徑繪製函式,可以建立非常複雜的形狀。
首先讓我們看看矩形。有三個函式可以在 canvas 上繪製矩形
fillRect(x, y, width, height)-
繪製一個填充矩形。
strokeRect(x, y, width, height)-
繪製一個矩形輪廓。
clearRect(x, y, width, height)-
清除指定的矩形區域,使其完全透明。
這三個函式都接受相同的引數。x 和 y 指定矩形左上角在 canvas 上的位置(相對於原點)。width 和 height 提供矩形的大小。
下面是上一頁的 draw() 函式,但現在它使用了這三個函式。
矩形形狀示例
function draw() {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
ctx.fillRect(25, 25, 100, 100);
ctx.clearRect(45, 45, 60, 60);
ctx.strokeRect(50, 50, 50, 50);
}
此示例的輸出如下所示。
fillRect() 函式繪製一個邊長為 100 畫素的大黑色正方形。然後 clearRect() 函式從中心清除一個 60x60 畫素的正方形,然後呼叫 strokeRect() 在清除的正方形內建立一個 50x50 畫素的矩形輪廓(概念上是 50x50;實際上是 52x52,下一節將解釋)。
在接下來的頁面中,我們將看到 clearRect() 的兩種替代方法,我們還將看到如何更改渲染形狀的顏色和描邊樣式。
與我們將在下一節中看到的路徑函式不同,所有三個矩形函式都立即繪製到 canvas 上。
邊緣模糊?
在上面的矩形示例以及所有即將到來的示例中,你可能會注意到形狀的邊緣可能比用 SVG 或 CSS 繪製的等效形狀顯得模糊。這並不是因為 canvas API 無法繪製銳利的邊緣,而是因為 canvas 網格對映到螢幕上的實際畫素的方式,以及在某些情況下,因為瀏覽器縮放 canvas 的方式。如果上述示例不夠明顯,讓我們使用 CSS 放大 canvas
<canvas id="canvas" width="15" height="15"></canvas>
#canvas {
width: 300px;
height: 300px;
}
function draw() {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
ctx.strokeRect(2, 2, 10, 10);
ctx.fillRect(7, 7, 1, 1);
}
在此示例中,我們建立了一個非常小的 canvas (15x15),但隨後使用 CSS 將其放大到 300x300 畫素。結果,每個 canvas 畫素現在由一個 20x20 的螢幕畫素塊表示。我們從 (2,2) 繪製一個描邊矩形到 (12,12),並從 (7,7) 繪製一個填充矩形到 (8,8)。它看起來真的很模糊。這是因為預設情況下,當瀏覽器縮放柵格影像時,它會使用平滑演算法來插值額外的畫素。這對於照片或具有捲曲邊緣的 canvas 圖形非常有用,但對於直邊形狀則不太理想。為了解決這個問題,我們可以將 image-rendering 設定為 pixelated
#canvas {
image-rendering: pixelated;
}
現在,當瀏覽器縮放 canvas 時,它會盡可能地保留原始影像的畫素化。
注意: image-rendering: pixelated 作為一種保留清晰邊緣的技術並非沒有問題。當 CSS 畫素與裝置畫素不一致時(如果 devicePixelRatio 不是整數),某些畫素可能會繪製得比其他畫素大,從而導致外觀不均勻。然而,這不是一個容易解決的問題,因為當 CSS 畫素無法準確對映到裝置畫素時,不可能精確地填充裝置畫素。
但現在出現了另一個問題,你也可以在原始矩形示例中觀察到:描邊矩形不僅寬 2 畫素而不是 1 畫素,而且看起來是灰色而不是預設的黑色。這是因為座標被解釋為形狀邊界的方式。
如果再次檢視上面的網格圖,你會發現像 2 或 12 這樣的座標並不是標識一個畫素,而是標識兩個畫素之間的邊緣。在下面的影像中,網格表示 canvas 座標網格。網格線之間的方塊是螢幕上的實際畫素。在下面的第一個網格影像中,一個從 (2,1) 到 (5,5) 的矩形被填充。它們之間的整個區域(淺紅色)落在畫素邊界上,因此最終填充的矩形將具有清晰的邊緣。

如果您考慮一條從 (3,1) 到 (3,5) 的路徑,線寬為 1.0,您將遇到第二張圖片中的情況。要填充的實際區域(深藍色)只延伸到路徑兩側畫素的一半。必須渲染此區域的近似值,這意味著這些畫素僅部分著色,並導致整個區域(淺藍色和深藍色)填充的顏色僅為實際描邊顏色的一半深。這就是在上面的矩形示例中 strokeRect() 呼叫中 1.0 寬度線發生的情況。
為了解決這個問題,你必須在建立路徑時非常精確。知道 1.0 寬度的線條將向路徑兩側延伸半個單位,從畫素的中心建立路徑將導致第三張圖片中的情況——1.0 線寬最終將完全精確地填充單個畫素的垂直線。
注意:請注意,在我們的垂直線示例中,Y 位置仍然引用了一個整數網格線位置——如果不是這樣,我們會在端點處看到半覆蓋的畫素。
所以這就是我們前面說矩形示例中的 strokeRect(50, 50, 50, 50) 呼叫在概念上是 50x50,但實際上是 52x52 的原因。輪廓的實際填充區域從 (49.5, 49.5) 開始,到 (100.5, 100.5) 結束,由於部分填充的畫素,實際填充區域是從 (49,49) 到 (101,101),即 52x52,並且邊緣寬 2 畫素。要獲得一個完全 50x50 的實心 1 畫素寬輪廓,你需要將矩形縮小輪廓的厚度 (1px),並將其移動輪廓厚度的一半 (0.5px)
function draw() {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
ctx.strokeRect(2.5, 2.5, 9, 9);
ctx.fillRect(7, 7, 1, 1);
}
對於偶數寬度的線條,每一半最終都是整數個畫素,因此您需要一條位於畫素之間的路徑(即 (3,1) 到 (3,5)),而不是位於畫素中間的路徑。
儘管最初處理可伸縮 2D 圖形時會有些痛苦,但關注畫素網格和路徑位置可確保無論涉及縮放或任何其他變換,您的繪圖都看起來正確。在正確位置繪製的 1.0 寬度垂直線在放大 2 倍時將變為清晰的 2 畫素線,並出現在正確位置。
這種部分填充畫素的現象也延伸到不與畫素網格對齊的形狀。例如,考慮一個旋轉的矩形(你將在下一節學習如何繪製它)。為了看看有和沒有 image-rendering: pixelated 的效果,我們並排放置了兩個 canvas,還有一個以全尺寸繪製的 canvas,帶網格線
function draw(canvasId) {
const canvas = document.getElementById(canvasId);
const ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(3, 2);
ctx.lineTo(9, 4.5);
ctx.lineTo(6.5, 10.5);
ctx.lineTo(0.5, 8);
ctx.closePath();
ctx.fill();
}
function drawFullScale() {
const canvas = document.getElementById("canvas3");
const ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(60, 40);
ctx.lineTo(180, 90);
ctx.lineTo(130, 210);
ctx.lineTo(10, 160);
ctx.closePath();
ctx.fill();
ctx.strokeStyle = "lightgray";
for (let i = 0; i < 16; i++) {
ctx.moveTo(i * 20, 0);
ctx.lineTo(i * 20, 300);
ctx.moveTo(0, i * 20);
ctx.lineTo(300, i * 20);
ctx.stroke();
}
}
如果放大影像會使其看起來比預期更模糊,那麼縮小影像會使其看起來更清晰。例如,如果您想讓 canvas 在螢幕上顯示為 300x150 畫素,您可以將其建立為 600x300 畫素,然後使用 CSS 將其縮小。這在高畫質螢幕(例如蘋果的 Retina 顯示器)上特別有用,其中一個 CSS 畫素由多個螢幕畫素表示,因此如果您忠實地繪製一個 300x150 畫素的 canvas,它將不會具有與頁面上其他元素相同的畫素解析度。
繪製路徑
現在讓我們看看路徑。路徑是點的列表,由不同形狀的線段連線,可以是彎曲的或不彎曲的,具有不同的寬度和不同的顏色。路徑,甚至子路徑,都可以閉合。要使用路徑建立形狀,我們需要採取一些額外的步驟
- 首先,建立路徑。
- 然後使用繪圖命令在路徑中繪圖。
- 路徑建立後,您可以描邊或填充路徑以進行渲染。
以下是用於執行這些步驟的函式
beginPath()-
建立一個新路徑。建立後,未來的繪圖命令將指向該路徑並用於構建該路徑。
- 路徑方法
-
設定物件不同路徑的方法。
closePath()-
向路徑新增一條直線,連線到當前子路徑的起點。
stroke()-
透過描邊輪廓來繪製形狀。
fill()-
透過填充路徑內容區域繪製實心形狀。
建立路徑的第一步是呼叫 beginPath()。在內部,路徑儲存為子路徑(直線、弧線等)列表,它們共同構成一個形狀。每次呼叫此方法時,列表都會重置,我們可以開始繪製新形狀。
注意:噹噹前路徑為空時,例如在呼叫 beginPath() 之後或在新建立的 canvas 上,第一個路徑構造命令始終被視為 moveTo(),無論它實際是什麼。因此,在重置路徑後,您幾乎總是希望明確設定起始位置。
第二步是呼叫實際指定要繪製路徑的方法。我們很快就會看到這些。
第三步(可選)是呼叫 closePath()。此方法嘗試透過從當前點到起點繪製一條直線來閉合形狀。如果形狀已閉合或列表中只有一個點,則此函式不執行任何操作。
注意:當您呼叫 fill() 時,任何開放形狀都會自動閉合,因此您不必呼叫 closePath()。但當您呼叫 stroke() 時,情況並非如此。
繪製三角形
例如,繪製三角形的程式碼可能看起來像這樣
function draw() {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(75, 50);
ctx.lineTo(100, 75);
ctx.lineTo(100, 25);
ctx.fill();
}
結果如下所示
移動畫筆
一個非常有用的函式,它實際上不繪製任何東西,但成為上面描述的路徑列表的一部分,是 moveTo() 函式。你最好把它想象成把鋼筆或鉛筆從一張紙上的一個點抬起來,然後放到下一個點。
moveTo(x, y)-
將畫筆移動到由
x和y指定的座標。
當 canvas 初始化或呼叫 beginPath() 時,您通常會希望使用 moveTo() 函式將起點放置在其他位置。我們也可以使用 moveTo() 繪製不連線的路徑。看看下面的笑臉。
要自己嘗試一下,您可以使用下面的程式碼片段。只需將其貼上到我們前面看到的 draw() 函式中即可。
function draw() {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.arc(75, 75, 50, 0, Math.PI * 2, true); // Outer circle
ctx.moveTo(110, 75);
ctx.arc(75, 75, 35, 0, Math.PI, false); // Mouth (clockwise)
ctx.moveTo(65, 65);
ctx.arc(60, 65, 5, 0, Math.PI * 2, true); // Left eye
ctx.moveTo(95, 65);
ctx.arc(90, 65, 5, 0, Math.PI * 2, true); // Right eye
ctx.stroke();
}
結果如下所示
如果你想看到連線線,可以刪除呼叫 moveTo() 的行。
注意:要了解有關 arc() 函式的更多資訊,請參閱下面的弧線部分。
直線
要繪製直線,請使用 lineTo() 方法。
lineTo(x, y)-
從當前繪圖位置到由
x和y指定的位置繪製一條直線。
此方法接受兩個引數,x 和 y,它們是線段終點的座標。起點取決於先前繪製的路徑,前一個路徑的終點是後續路徑的起點,依此類推。起點也可以透過使用 moveTo() 方法進行更改。
下面的示例繪製了兩個三角形,一個填充,一個描邊。
function draw() {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
// Filled triangle
ctx.beginPath();
ctx.moveTo(25, 25);
ctx.lineTo(105, 25);
ctx.lineTo(25, 105);
ctx.fill();
// Stroked triangle
ctx.beginPath();
ctx.moveTo(125, 125);
ctx.lineTo(125, 45);
ctx.lineTo(45, 125);
ctx.closePath();
ctx.stroke();
}
這透過呼叫 beginPath() 開始一個新的形狀路徑。然後我們使用 moveTo() 方法將起點移動到所需位置。在這下面,繪製了兩條線,構成三角形的兩條邊。
您會注意到填充三角形和描邊三角形之間的區別。如上所述,這是因為當路徑填充時形狀會自動閉合,但當它們被描邊時則不會。如果我們遺漏了描邊三角形的 closePath(),那麼只會繪製兩條線,而不是一個完整的三角形。
弧線
要繪製弧線或圓形,我們使用 arc() 或 arcTo() 方法。
arc(x, y, radius, startAngle, endAngle, counterclockwise)-
繪製一個以 (x, y) 位置為中心,半徑為 r,從 startAngle 開始,到 endAngle 結束的弧,方向由 counterclockwise 指示(預設為順時針)。
arcTo(x1, y1, x2, y2, radius)-
繪製一個帶有給定控制點和半徑的弧,透過直線連線到前一個點。
讓我們更詳細地瞭解一下 arc 方法,它接受六個引數:x 和 y 是弧線所在圓的中心的座標。radius 不言自明。startAngle 和 endAngle 引數以弧度定義弧線在圓曲線上的起點和終點。這些是從 x 軸測量的。counterclockwise 引數是一個布林值,當為 true 時,弧線逆時針繪製;否則,弧線順時針繪製。
注意:arc 函式中的角度以弧度而不是度數測量。要將度數轉換為弧度,您可以使用以下 JavaScript 表示式:radians = (Math.PI/180)*degrees。
下面的例子比我們上面看到的例子稍微複雜一些。它繪製了 12 個具有不同角度和填充的不同弧。
這兩個 for 迴圈用於遍歷弧的行和列。對於每個弧,我們透過呼叫 beginPath() 開始一個新路徑。在程式碼中,弧的每個引數都放在一個變數中以提高畫質晰度,但您在實際生活中不一定會這樣做。
x 和 y 座標應該足夠清楚。radius 和 startAngle 是固定的。endAngle 在第一列從 180 度(半圓)開始,並以 90 度步長增加,最終在最後一列形成一個完整的圓。
clockwise 引數的語句導致第一行和第三行繪製為順時針弧,第二行和第四行繪製為逆時針弧。最後,if 語句使上半部分為描邊弧,下半部分為填充弧。
注意:此示例需要比本頁其他示例稍大的 canvas:150 x 200 畫素。
function draw() {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 3; j++) {
ctx.beginPath();
const x = 25 + j * 50; // x coordinate
const y = 25 + i * 50; // y coordinate
const radius = 20; // Arc radius
const startAngle = 0; // Starting point on circle
const endAngle = Math.PI + (Math.PI * j) / 2; // End point on circle
const counterclockwise = i % 2 !== 0; // clockwise or counterclockwise
ctx.arc(x, y, radius, startAngle, endAngle, counterclockwise);
if (i > 1) {
ctx.fill();
} else {
ctx.stroke();
}
}
}
}
貝塞爾曲線和二次曲線
下一種可用的路徑型別是 貝塞爾曲線,有三次和二次兩種型別。這些通常用於繪製複雜的有機形狀。
quadraticCurveTo(cp1x, cp1y, x, y)-
從當前畫筆位置到由
x和y指定的終點繪製二次貝塞爾曲線,使用由cp1x和cp1y指定的控制點。 bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)-
從當前畫筆位置到由
x和y指定的終點繪製三次貝塞爾曲線,使用由 (cp1x,cp1y) 和 (cp2x,cp2y) 指定的控制點。
這兩者之間的區別在於,二次貝塞爾曲線有一個起點和終點(藍色點)和一個控制點(由紅色點表示),而三次貝塞爾曲線使用兩個控制點。
這兩個方法中的 x 和 y 引數是終點的座標。cp1x 和 cp1y 是第一個控制點的座標,cp2x 和 cp2y 是第二個控制點的座標。
使用二次和三次貝塞爾曲線可能相當具有挑戰性,因為與 Adobe Illustrator 等向量繪圖軟體不同,我們沒有直接的視覺反饋來了解我們正在做什麼。這使得繪製複雜形狀變得相當困難。在下面的示例中,我們將繪製一些簡單的有機形狀,但如果您有時間,最重要的是有耐心,則可以建立更復雜的形狀。
這些例子沒什麼特別難的。在這兩種情況下,我們都看到了一系列曲線的繪製,最終形成了一個完整的形狀。
二次貝塞爾曲線
此示例使用多個二次貝塞爾曲線渲染一個語音氣泡。
function draw() {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
// Quadratic curves example
ctx.beginPath();
ctx.moveTo(75, 25);
ctx.quadraticCurveTo(25, 25, 25, 62.5);
ctx.quadraticCurveTo(25, 100, 50, 100);
ctx.quadraticCurveTo(50, 120, 30, 125);
ctx.quadraticCurveTo(60, 120, 65, 100);
ctx.quadraticCurveTo(125, 100, 125, 62.5);
ctx.quadraticCurveTo(125, 25, 75, 25);
ctx.stroke();
}
三次貝塞爾曲線
此示例使用三次貝塞爾曲線繪製一個心形。
function draw() {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
// Cubic curves example
ctx.beginPath();
ctx.moveTo(75, 40);
ctx.bezierCurveTo(75, 37, 70, 25, 50, 25);
ctx.bezierCurveTo(20, 25, 20, 62.5, 20, 62.5);
ctx.bezierCurveTo(20, 80, 40, 102, 75, 120);
ctx.bezierCurveTo(110, 102, 130, 80, 130, 62.5);
ctx.bezierCurveTo(130, 62.5, 130, 25, 100, 25);
ctx.bezierCurveTo(85, 25, 75, 37, 75, 40);
ctx.fill();
}
矩形
除了我們在繪製矩形中看到的三種直接在畫布上繪製矩形的方法外,還有 rect() 方法,它將一個矩形路徑新增到當前開放的路徑中。
rect(x, y, width, height)-
繪製一個矩形,其左上角由 (
x,y) 指定,並具有指定的width和height。
在執行此方法之前,會自動呼叫 moveTo() 方法,引數為 (x,y)。換句話說,當前畫筆位置會自動重置為預設座標。
組合
到目前為止,此頁面上的每個示例都只使用每種形狀的一種路徑函式。然而,您可以使用建立形狀的路徑的數量或型別沒有限制。因此,在這個最終示例中,讓我們結合所有路徑函式來製作一組非常著名的遊戲角色。
function draw() {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
roundedRect(ctx, 12, 12, 184, 168, 15);
roundedRect(ctx, 19, 19, 170, 154, 9);
roundedRect(ctx, 53, 53, 49, 33, 10);
roundedRect(ctx, 53, 119, 49, 16, 6);
roundedRect(ctx, 135, 53, 49, 33, 10);
roundedRect(ctx, 135, 119, 25, 49, 10);
ctx.beginPath();
ctx.arc(37, 37, 13, Math.PI / 7, -Math.PI / 7, false);
ctx.lineTo(31, 37);
ctx.fill();
for (let i = 0; i < 8; i++) {
ctx.fillRect(51 + i * 16, 35, 4, 4);
}
for (let i = 0; i < 6; i++) {
ctx.fillRect(115, 51 + i * 16, 4, 4);
}
for (let i = 0; i < 8; i++) {
ctx.fillRect(51 + i * 16, 99, 4, 4);
}
ctx.beginPath();
ctx.moveTo(83, 116);
ctx.lineTo(83, 102);
ctx.bezierCurveTo(83, 94, 89, 88, 97, 88);
ctx.bezierCurveTo(105, 88, 111, 94, 111, 102);
ctx.lineTo(111, 116);
ctx.lineTo(106.333, 111.333);
ctx.lineTo(101.666, 116);
ctx.lineTo(97, 111.333);
ctx.lineTo(92.333, 116);
ctx.lineTo(87.666, 111.333);
ctx.lineTo(83, 116);
ctx.fill();
ctx.fillStyle = "white";
ctx.beginPath();
ctx.moveTo(91, 96);
ctx.bezierCurveTo(88, 96, 87, 99, 87, 101);
ctx.bezierCurveTo(87, 103, 88, 106, 91, 106);
ctx.bezierCurveTo(94, 106, 95, 103, 95, 101);
ctx.bezierCurveTo(95, 99, 94, 96, 91, 96);
ctx.moveTo(103, 96);
ctx.bezierCurveTo(100, 96, 99, 99, 99, 101);
ctx.bezierCurveTo(99, 103, 100, 106, 103, 106);
ctx.bezierCurveTo(106, 106, 107, 103, 107, 101);
ctx.bezierCurveTo(107, 99, 106, 96, 103, 96);
ctx.fill();
ctx.fillStyle = "black";
ctx.beginPath();
ctx.arc(101, 102, 2, 0, Math.PI * 2, true);
ctx.fill();
ctx.beginPath();
ctx.arc(89, 102, 2, 0, Math.PI * 2, true);
ctx.fill();
}
// A utility function to draw a rectangle with rounded corners.
function roundedRect(ctx, x, y, width, height, radius) {
ctx.beginPath();
ctx.moveTo(x, y + radius);
ctx.arcTo(x, y + height, x + radius, y + height, radius);
ctx.arcTo(x + width, y + height, x + width, y + height - radius, radius);
ctx.arcTo(x + width, y, x + width - radius, y, radius);
ctx.arcTo(x, y, x, y + radius, radius);
ctx.stroke();
}
生成的影像如下所示
我們不會詳細介紹這一點,因為它實際上出奇地簡單。最重要的事情是注意繪圖上下文上 fillStyle 屬性的使用,以及實用函式(在本例中為 roundedRect())的使用。對於您經常做的繪圖位使用實用函式非常有幫助,並且可以減少您需要的程式碼量及其複雜性。
我們將在本教程的後面更詳細地介紹 fillStyle。在這裡,我們所做的只是用它將路徑的填充顏色從預設的黑色更改為白色,然後再更改回來。
帶孔的形狀
要繪製帶孔的形狀,我們需要在繪製外部形狀時以不同的順時針方向繪製孔。我們或者順時針繪製外部形狀,逆時針繪製內部形狀;或者逆時針繪製外部形狀,順時針繪製內部形狀。
function draw() {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
ctx.beginPath();
// Outer shape clockwise ⟳
ctx.moveTo(0, 0);
ctx.lineTo(150, 0);
ctx.lineTo(75, 129.9);
// Inner shape anticlockwise ↺
ctx.moveTo(75, 20);
ctx.lineTo(50, 60);
ctx.lineTo(100, 60);
ctx.fill();
}
在上面的例子中,外三角形順時針方向(移動到左上角,然後畫一條線到右上角,最後到底部),內三角形逆時針方向(移動到頂部,然後畫一條線到左下角,最後到右下角)。
Path2D 物件
正如我們在上一個示例中看到的那樣,可以有一系列路徑和繪圖命令來在 canvas 上繪製物件。為了簡化程式碼和提高效能,瀏覽器最新版本中提供的 Path2D 物件允許您快取或記錄這些繪圖命令。您可以快速回放您的路徑。讓我們看看如何構造一個 Path2D 物件
new Path2D(); // empty path object
new Path2D(path); // copy from another Path2D object
new Path2D(d); // path from SVG path data
所有路徑方法,如 moveTo、rect、arc 或 quadraticCurveTo 等,我們上面瞭解到的,都可以在 Path2D 物件上使用。
Path2D API 還增加了一種使用 addPath 方法組合路徑的方法。這在您想從多個元件構建物件時很有用,例如。
Path2D.addPath(path [, transform])-
將路徑新增到當前路徑,帶可選的變換矩陣。
Path2D 示例
在這個示例中,我們建立了一個矩形和一個圓形。兩者都儲存為 Path2D 物件,以便它們可以供以後使用。隨著新的 Path2D API 的出現,一些方法已更新,可以可選地接受 Path2D 物件以代替當前路徑。例如,這裡使用帶有路徑引數的 stroke 和 fill 將這兩個物件繪製到 canvas 上。
function draw() {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const rectangle = new Path2D();
rectangle.rect(10, 10, 50, 50);
const circle = new Path2D();
circle.arc(100, 35, 25, 0, 2 * Math.PI);
ctx.stroke(rectangle);
ctx.fill(circle);
}
使用 SVG 路徑
新的 canvas Path2D API 的另一個強大功能是使用 SVG 路徑資料來初始化 canvas 上的路徑。這可能允許您在 SVG 和 canvas 中傳遞和重用路徑資料。
路徑將移動到點 (M10 10),然後水平向右移動 80 個點 (h 80),然後向下移動 80 個點 (v 80),然後向左移動 80 個點 (h -80),然後回到起點 (z)。您可以在 Path2D 建構函式頁面上檢視此示例。
const p = new Path2D("M10 10 h 80 v 80 h -80 Z");