|
Posted on 2012-04-15 16:37 zljpp 閱讀(156) 評(píng)論(0) 編輯 收藏
HTML5 canvas 元素詳細(xì)教程二:繪制圖形。
![\" src=]() 在開始之前,我們需要對(duì)canvas 的網(wǎng)格(grid)或者坐標(biāo)空間( coordinate space)進(jìn)行探討。在 HTML5 canvas 元素詳細(xì)教程一中有一個(gè)150像素寬, 150像素高的 canvas 對(duì)象的HTML模板。在畫面上疊加上默認(rèn)網(wǎng)格,如右圖。通常網(wǎng)格的1個(gè)單元對(duì)應(yīng) canvas 上的1個(gè)像素。網(wǎng)格的原點(diǎn)是定位在左上角(坐標(biāo)(0,0))。畫面里的所有物體的位置都是相對(duì)這個(gè)原點(diǎn)。這樣,左上角的藍(lán)色方塊的位置就是距左邊x像素和距上邊Y像素(坐標(biāo)(x, y))。現(xiàn)在我們使用默認(rèn)的狀態(tài)來進(jìn)行圖形的繪制吧。
矩形 Rectangles
首先來詳細(xì)介紹矩形的繪制吧,矩形有三個(gè)函數(shù)可以繪制的:
fillRect(x,y,width,height) : Draws a filled rectangle strokeRect(x,y,width,height) : Draws a rectangular outline clearRect(x,y,width,height) : Clears the specified area and makes it fully transparent
上面的函數(shù)都接受四個(gè)參數(shù),X和Y用于指定矩形左上角也就是相對(duì)于原點(diǎn)的位置,width和height用于指定矩形的寬和高。這對(duì)于有基礎(chǔ)的同學(xué),還是非常簡(jiǎn)單的。下面,我們使用HTML5 canvas 元素詳細(xì)教程一里提供的 draw() 函數(shù),我們添加了上面的三個(gè)函數(shù)。
繪制矩形的例子 Rectangular shape example
運(yùn)行的結(jié)果應(yīng)該和右邊圖形一模一樣的。fillRect 函數(shù)畫了100x100黑色矩形,clearRect 函數(shù)清空了中間 60x60 大小的方塊,然后strokeRect 函數(shù)又在清空了的空間畫出了一個(gè) 50x50 的矩形邊框。。
繪制路徑 Drawing paths
繪制路徑不像繪制矩形那要,需要一些額外的步驟,下面是需要使用的函數(shù)和步驟:
beginPath() closePath() stroke() fill()
第一步:用 beginPath 創(chuàng)建路徑。在在內(nèi)存中,徑路是以一組子路徑(直線,弧線等)的形式儲(chǔ)存的,它們共同構(gòu)成一個(gè)圖形。每次調(diào)用 beginPath,子路徑組都會(huì)被重置,然后可以繪制新的圖形。
第二步:是實(shí)際繪制路徑的部分,很快我們就會(huì)看到。
第三步:調(diào)用 closePath 方法,它會(huì)嘗試用直線連接當(dāng)前端點(diǎn)與起始端點(diǎn)來關(guān)閉路徑,但如果圖形已經(jīng)關(guān)閉或者只有一個(gè)點(diǎn),它會(huì)什么都不做。這一步不是必須的。
第四部:也就是最后一步,調(diào)用 stroke或 fill 方法,這時(shí),圖形才是實(shí)際的繪制到 canvas上去。stroke是繪制圖形的邊框,fill會(huì)用填充出一個(gè)實(shí)心圖形。當(dāng)調(diào)用 fill 時(shí),開放的路徑會(huì)自動(dòng)閉合,而無須調(diào)用 closePath ,這需要大家注意。
畫一個(gè)簡(jiǎn)單圖形(如三角形)的代碼如下。
ctx.beginPath(); ctx.moveTo(75,50); ctx.lineTo(100,75); ctx.lineTo(100,25); ctx.fill();
moveTo 是一個(gè)十分有用的方法,它是繪制路徑的實(shí)用方法的一部分。你可以把它想象成是把筆提起,并從一個(gè)點(diǎn)移動(dòng)到另一個(gè)點(diǎn)的過程。
moveTo(x, y)
它接受 x 和 y (新的坐標(biāo)位置)作為參數(shù)。
當(dāng)進(jìn)行 canvas 初始化或者調(diào)用 beginPath 的時(shí)候,起始坐標(biāo)設(shè)置就是原點(diǎn)(0,0)。許多的情況下,我 moveTo 方法將起始坐標(biāo)移至其它地方,或者用于繪制不連續(xù)的路徑。看看右邊的笑臉,紅線就是使用 moveTo 移動(dòng)的軌跡。
把下面的moveTo 的使用示例代碼粘貼到之前用過的 draw 函數(shù)內(nèi)在看看效果吧。
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 方法接受終點(diǎn)的坐標(biāo)(x,y)作為參數(shù)。起始坐標(biāo)取決于前一路徑,前一路徑的終點(diǎn)即當(dāng)前路徑的起點(diǎn),起始坐標(biāo)也可以通過 moveTo 方法來設(shè)置。
lineTo(x, y)
lineTo 的使用示例
示例(如右圖)畫的是兩個(gè)三角形,一個(gè)實(shí)色填充,一個(gè)勾邊。首先調(diào)用 beginPath 方法創(chuàng)建一個(gè)新路徑,然后用moveTo 方法將起始坐標(biāo)移至想要的位置,然后畫兩條直線來構(gòu)成三角形的兩條邊。
可以注意到 fill 和 strok 繪三角形的區(qū)別,使用 fill 路徑會(huì)自動(dòng)閉合,但使用 stroke 不會(huì),如果不關(guān)閉路徑,勾畫出來的只有兩邊。
//填充三角形 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)
該方法接受五個(gè)參數(shù): 1、,y 是圓心坐標(biāo); 2、radius 是半徑; 3、startAngle是起弧度(以 x 軸為基準(zhǔn)); 4、endAngle 是末弧度(以 x 軸為基準(zhǔn)); 5、anticlockwise 為 true 表示逆時(shí)針,反之順時(shí)針。
警告:在 Firefox 的 beta 版本里,最后一個(gè)參數(shù)是 clockwise,而最終版本不是。因此如果是從 beta 升級(jí)至發(fā)行版需要做相應(yīng)修改。
注意:arc 方法里用到的角度是以弧度為單位而不是度。度和弧度直接的轉(zhuǎn)換可以用這個(gè)表達(dá)式:var radians = (Math.PI/180)*degrees;。
arc 的使用示例
這個(gè)示例比之前見到過的要復(fù)雜一些,畫了12個(gè)不同的弧形,有不同夾角和填充狀態(tài)的。如果我用上面畫笑臉的方式來畫這些弧形,那會(huì)是一大段的代碼,而且,畫每一個(gè)弧形時(shí)我都需要知道其圓心位置。像我這里畫 90,180 和 270 度的弧形看起來不是很麻煩,但是如果圖形更復(fù)雜一些,則實(shí)現(xiàn)起來會(huì)越來越困難。
這里使用兩個(gè) for 循環(huán)來畫多行多列的弧形。每一個(gè)弧形都用 beginPath 方法創(chuàng)建一個(gè)新路徑。然后為了方便閱讀和理解,我把所有參數(shù)都寫成變量形式。顯而易見,x 和 y 作為圓心坐標(biāo)。 radius 和 startAngle 都是固定,endAngle 從 180 度半圓開始,以 90 度方式遞增至圓。anticlockwise 則取決于奇偶行數(shù)。
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 ---//修復(fù)錯(cuò)誤標(biāo)點(diǎn) 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列,要把畫布擴(kuò)大到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
貝塞爾曲線可以是二次和三次方的形式,常用于繪制復(fù)雜而有規(guī)律的形狀。
quadraticCurveTo(cp1x, cp1y, x, y) bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
兩行代碼的區(qū)別見右圖。它們都是一個(gè)起點(diǎn)一個(gè)終點(diǎn)(圖中的藍(lán)點(diǎn)),但二次方貝塞爾曲線只有一個(gè)(紅色)控制點(diǎn)點(diǎn))而三次方貝塞爾曲線有兩個(gè)。
參數(shù) x 和 y 是終點(diǎn)坐標(biāo),cp1x 和 cp1y 是第一個(gè)控制點(diǎn)的坐標(biāo),cp2x 和 cp2y 是第二個(gè)的。
使用二次方和三次方的貝塞爾曲線是相當(dāng)有挑戰(zhàn)的,因?yàn)椴幌裨谑噶坷L圖軟件 Adobe Illustrator 里那樣有即時(shí)的視覺反饋。因?yàn)橛盟鼇懋嫃?fù)雜圖形是比較麻煩的。但如果你有時(shí)間,并且最重要是有耐心,再?gòu)?fù)雜的圖形都可以繪制出來的。下面我們來畫一個(gè)簡(jiǎn)單而又規(guī)律的圖形。
這些例子都比較簡(jiǎn)單。我們繪制的都是完整的圖形。
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();
通過計(jì)算,可以由二次曲線的單個(gè)控制點(diǎn)得出相應(yīng)三次方曲線的兩個(gè)控制點(diǎn),因此二次方轉(zhuǎn)三次方是可能的,但是反之不然。僅當(dāng)三次方程中的三次項(xiàng)為零是才可能轉(zhuǎn)換為二次的貝塞爾曲線。通常地可以用多條二次方曲線通過細(xì)分算法來近似模擬三次方貝塞爾曲線。
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
除了上面提到的三個(gè)方法可以直接繪制矩形之外,我們還有一個(gè) rect 方法是用于繪制矩形路徑的。
rect(x, y, width, height)
它接受四個(gè)參數(shù),x 和 y 是其左上角坐標(biāo),width 和 height 是其寬和高。
當(dāng)它被調(diào)用時(shí),moveTo 方法會(huì)自動(dòng)被調(diào)用,參數(shù)為(0,0),于是起始坐標(biāo)又恢復(fù)成初始原點(diǎn)了。
綜合 Making combinations
我們上面的示例都只用到了一種類型的路徑,當(dāng)然 canvas 不會(huì)限制所使用的路徑類型的多少。
綜合樣例
在整個(gè)例子里,最值得注意的是 roundedRect 函數(shù)的使用和 fillStyle 屬性的設(shè)置。自定義函數(shù)對(duì)于封裝復(fù)雜圖形的繪制是非常有用的。在這個(gè)例子里使用自定義函數(shù)就省掉了大約一半的代碼。
在接下來的例子里會(huì)深入探討 fillStyle 屬性的使用。這里是用它來改變填充顏色,從默認(rèn)的黑色,到白色,然后再回到黑色。
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(); }
|