HTML5 canvas 元素詳細教程二:繪制圖形。
矩形 Rectangles
首先來詳細介紹矩形的繪制吧,矩形有三個函數可以繪制的:
strokeRect(x,y,width,height) : Draws a rectangular outline
clearRect(x,y,width,height) : Clears the specified area and makes it fully transparent
上面的函數都接受四個參數,X和Y用于指定矩形左上角也就是相對于原點的位置,width和height用于指定矩形的寬和高。這對于有基礎的同學,還是非常簡單的。下面,我們使用HTML5 canvas 元素詳細教程一里提供的 draw() 函數,我們添加了上面的三個函數。

繪制矩形的例子 Rectangular shape example
運行的結果應該和右邊圖形一模一樣的。fillRect 函數畫了100x100黑色矩形,clearRect 函數清空了中間 60x60 大小的方塊,然后strokeRect 函數又在清空了的空間畫出了一個 50x50 的矩形邊框。。
繪制路徑 Drawing paths
繪制路徑不像繪制矩形那要,需要一些額外的步驟,下面是需要使用的函數和步驟:
beginPath()
closePath()
stroke()
fill()
第一步:用 beginPath 創建路徑。在在內存中,徑路是以一組子路徑(直線,弧線等)的形式儲存的,它們共同構成一個圖形。每次調用 beginPath,子路徑組都會被重置,然后可以繪制新的圖形。
第二步:是實際繪制路徑的部分,很快我們就會看到。
第三步:調用 closePath 方法,它會嘗試用直線連接當前端點與起始端點來關閉路徑,但如果圖形已經關閉或者只有一個點,它會什么都不做。這一步不是必須的。
第四部:也就是最后一步,調用 stroke或 fill 方法,這時,圖形才是實際的繪制到 canvas上去。stroke是繪制圖形的邊框,fill會用填充出一個實心圖形。當調用 fill 時,開放的路徑會自動閉合,而無須調用 closePath ,這需要大家注意。
畫一個簡單圖形(如三角形)的代碼如下。
ctx.beginPath();
ctx.moveTo(75,50);
ctx.lineTo(100,75);
ctx.lineTo(100,25);
ctx.fill();
moveTo(x, y)
它接受 x 和 y (新的坐標位置)作為參數。
當進行 canvas 初始化或者調用 beginPath 的時候,起始坐標設置就是原點(0,0)。許多的情況下,我 moveTo 方法將起始坐標移至其它地方,或者用于繪制不連續的路徑。看看右邊的笑臉,紅線就是使用 moveTo 移動的軌跡。

把下面的moveTo 的使用示例代碼粘貼到之前用過的 draw 函數內在看看效果吧。
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();
//thegoneheart 完整例子
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();
ctx.beginPath();
ctx.moveTo(40,75);
ctx.lineTo(60,65);
ctx.lineTo(90,65);
ctx.moveTo(110,75);
ctx.lineTo(125,75);
ctx.stroke();
注意:你可以注釋 moveTo 方法來觀察那些連接起來的線。
注意:arc 方法的用法見下面。
繪制各種線條 Lines
在這里使用lineTo 方法來畫直線。lineTo 方法接受終點的坐標(x,y)作為參數。起始坐標取決于前一路徑,前一路徑的終點即當前路徑的起點,起始坐標也可以通過 moveTo 方法來設置。
lineTo(x, y)
lineTo 的使用示例
示例(如右圖)畫的是兩個三角形,一個實色填充,一個勾邊。首先調用 beginPath 方法創建一個新路徑,然后用moveTo 方法將起始坐標移至想要的位置,然后畫兩條直線來構成三角形的兩條邊。
可以注意到 fill 和 strok 繪三角形的區別,使用 fill 路徑會自動閉合,但使用 stroke 不會,如果不關閉路徑,勾畫出來的只有兩邊。
//填充三角形
ctx.beginPath();
ctx.moveTo(25,25);
ctx.lineTo(105,25);
ctx.lineTo(25,105);
ctx.fill();
// 勾邊三角形
ctx.beginPath();
ctx.moveTo(125,125);
ctx.lineTo(125,45);
ctx.lineTo(45,125);
ctx.closePath();
ctx.stroke();
弧線 Arcs
arc 方法是來繪制弧線或圓。
arc(x, y, radius, startAngle, endAngle, anticlockwise)
arc(x, y, radius, startAngle, endAngle, anticlockwise)
2、radius 是半徑;
3、startAngle是起弧度(以 x 軸為基準);
4、endAngle 是末弧度(以 x 軸為基準);
5、anticlockwise 為 true 表示逆時針,反之順時針。
警告:在 Firefox 的 beta 版本里,最后一個參數是 clockwise,而最終版本不是。因此如果是從 beta 升級至發行版需要做相應修改。
注意:arc 方法里用到的角度是以弧度為單位而不是度。度和弧度直接的轉換可以用這個表達式:var radians = (Math.PI/180)*degrees;。
arc 的使用示例

這個示例比之前見到過的要復雜一些,畫了12個不同的弧形,有不同夾角和填充狀態的。如果我用上面畫笑臉的方式來畫這些弧形,那會是一大段的代碼,而且,畫每一個弧形時我都需要知道其圓心位置。像我這里畫 90,180 和 270 度的弧形看起來不是很麻煩,但是如果圖形更復雜一些,則實現起來會越來越困難。
這里使用兩個 for 循環來畫多行多列的弧形。每一個弧形都用 beginPath 方法創建一個新路徑。然后為了方便閱讀和理解,我把所有參數都寫成變量形式。顯而易見,x 和 y 作為圓心坐標。 radius 和 startAngle 都是固定,endAngle 從 180 度半圓開始,以 90 度方式遞增至圓。anticlockwise 則取決于奇偶行數。
for (i=0;i<4;i++){
for(j=0;j<3;j++){ //chinese_xu 原始代碼
ctx.beginPath();
var x = 25+j*50; // x coordinate
var y = 25+i*50; // y coordinate
var radius = 20; // Arc radius
var startAngle = 0; // Starting point on circle
var endAngle = Math.PI+(Math.PI*j)/2; // End point on circle ---//修復錯誤標點
var anticlockwise = i%2==0 ? false : true; // clockwise or anticlockwise
ctx.arc(x,y,radius,startAngle,endAngle, anticlockwise);
if (i>1){
ctx.fill();
} else {
ctx.stroke();
}
}
}
//chinese_xu 原始代碼并沒有按照1/4圓遞增來畫。
//修改后輸出4行4列,要把畫布擴大到200*200觀看
for (i=0;i<4;i++){
for(j=0;j<4;j++){
ctx.beginPath();
var x = 25+j*50; // x coordinate
var y = 25+i*50; // y coordinate
var radius = 20; // Arc radius
var startAngle = 0; // Starting point on circle
var endAngle = Math.PI*(2-j/2); // End point on circle
var anticlockwise = i%2==0 ? false : true; // clockwise or anticlockwise
ctx.arc(x,y,radius,startAngle,endAngle, anticlockwise);
if (i>1){
ctx.fill();
} else {
ctx.stroke();
}
}
}
貝塞爾和二次方曲線 Bezier and quadratic curves
貝塞爾曲線可以是二次和三次方的形式,常用于繪制復雜而有規律的形狀。
quadraticCurveTo(cp1x, cp1y, x, y)
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)

兩行代碼的區別見右圖。它們都是一個起點一個終點(圖中的藍點),但二次方貝塞爾曲線只有一個(紅色)控制點點)而三次方貝塞爾曲線有兩個。
參數 x 和 y 是終點坐標,cp1x 和 cp1y 是第一個控制點的坐標,cp2x 和 cp2y 是第二個的。
使用二次方和三次方的貝塞爾曲線是相當有挑戰的,因為不像在矢量繪圖軟件 Adobe Illustrator 里那樣有即時的視覺反饋。因為用它來畫復雜圖形是比較麻煩的。但如果你有時間,并且最重要是有耐心,再復雜的圖形都可以繪制出來的。下面我們來畫一個簡單而又規律的圖形。
這些例子都比較簡單。我們繪制的都是完整的圖形。
quadraticCurveTo 的使用示例
// Quadratric 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();

通過計算,可以由二次曲線的單個控制點得出相應三次方曲線的兩個控制點,因此二次方轉三次方是可能的,但是反之不然。僅當三次方程中的三次項為零是才可能轉換為二次的貝塞爾曲線。通常地可以用多條二次方曲線通過細分算法來近似模擬三次方貝塞爾曲線。
bezierCurveTo 的使用示例

// Bezier 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();
矩形路徑 Rectangles
除了上面提到的三個方法可以直接繪制矩形之外,我們還有一個 rect 方法是用于繪制矩形路徑的。
rect(x, y, width, height)
它接受四個參數,x 和 y 是其左上角坐標,width 和 height 是其寬和高。
當它被調用時,moveTo 方法會自動被調用,參數為(0,0),于是起始坐標又恢復成初始原點了。
綜合 Making combinations
我們上面的示例都只用到了一種類型的路徑,當然 canvas 不會限制所使用的路徑類型的多少。
綜合樣例
在整個例子里,最值得注意的是 roundedRect 函數的使用和 fillStyle 屬性的設置。自定義函數對于封裝復雜圖形的繪制是非常有用的。在這個例子里使用自定義函數就省掉了大約一半的代碼。
在接下來的例子里會深入探討 fillStyle 屬性的使用。這里是用它來改變填充顏色,從默認的黑色,到白色,然后再回到黑色。
function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
roundedRect(ctx,12,12,150,150,15);
roundedRect(ctx,19,19,150,150,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); //chiensexu 本來是true呵呵,反了
ctx.lineTo(31,37);
ctx.fill();
for(i=0;i<8;i++){
ctx.fillRect(51+i*16,35,4,4);
}
for(i=0;i<6;i++){
ctx.fillRect(115,51+i*16,4,4);
}
for(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();
}
function roundedRect(ctx,x,y,width,height,radius){
ctx.beginPath();
ctx.moveTo(x,y+radius);
ctx.lineTo(x,y+height-radius);
ctx.quadraticCurveTo(x,y+height,x+radius,y+height);
ctx.lineTo(x+width-radius,y+height);
ctx.quadraticCurveTo(x+width,y+height,x+width,y+height-radius);
ctx.lineTo(x+width,y+radius);
ctx.quadraticCurveTo(x+width,y,x+width-radius,y);
ctx.lineTo(x+radius,y);
ctx.quadraticCurveTo(x,y,x,y+radius);
ctx.stroke();
}