翻譯自:https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Drawing_shapescanvas
柵格ide
在咱們開始畫圖以前,咱們須要瞭解一下畫布柵格(canvas grid)以及座標空間。上一頁中的HTML模板中有個寬高150px的canvas元素。如右圖所示,canvas元素默認被網格所覆蓋。一般來講網格中的一個單元至關於canvas元素中的一像素。柵格的起點爲左上角(座標爲(0,0))。全部元素的位置都相對於原點定位。因此圖中藍色方形左上角的座標爲距離左邊(Y軸)x像素,距離上邊(X軸)y像素(座標爲(x,y))。在課程的最後咱們會平移原點到不一樣的座標上,旋轉網格以及縮放。如今咱們仍是使用原來的設置。函數
繪製矩形
不一樣於SVG,HTML中的元素canvas只支持一種原生的圖形繪製:矩形。全部其餘的圖形的繪製都至少須要生成一條路徑。不過,咱們擁有衆多路徑生成的方法讓複雜圖形的繪製成爲了可能。學習
首先,咱們回到矩形的繪製中。canvas提供了三種方法繪製矩形:ui
fillRect(x, y, width, height)
繪製一個填充的矩形
strokeRect(x, y, width, height)
繪製一個矩形的邊框
clearRect(x, y, width, height)
清除指定矩形區域,讓清除部分徹底透明。
上面提供的方法之中每個都包含了相同的參數。x與y指定了在canvas畫布上所繪製的矩形的左上角(相對於原點)的座標。width和height設置矩形的尺寸。spa
下面的draw() 函數是前一頁中取得的,如今就來使用上面的三個函數。翻譯
矩形(Rectangular)例子3d
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');blog
ctx.fillRect(25,25,100,100);
ctx.clearRect(45,45,60,60);
ctx.strokeRect(50,50,50,50);
}
}
該例子的輸出以下圖所示。遊戲
fillRect()函數繪製了一個邊長爲100px的黑色正方形。clearRect()函數從正方形的中心開始擦除了一個60*60px的正方形,接着strokeRect()在清除區域內生成一個50*50的正方形邊框。
接下來咱們可以看到clearRect()的兩個可選方法,而後咱們會知道如何改變渲染圖形的填充顏色及描邊顏色。
不一樣於下一節所要介紹的路徑函數(path function),以上的三個函數繪製以後會立刻顯如今canvas上,即時生效。
繪製路徑
使用路徑繪製圖形須要一些額外的步驟。首先,須要生成路徑。而後再路徑上使用繪圖命令繪製。以後閉合路徑。一旦路徑生成,你就能經過描邊或填充路徑來渲染圖形。如下是所要用到的函數:
beginPath()
新建一條路徑,生成以後,圖形繪製命令被指向到路徑上生成路徑。
closePath()
閉合路徑以後圖形繪製命令有從新指向到上下文中。
stroke()
經過線條來繪製圖形輪廓。
fill()
經過填充路徑的內容區域生成圖形。
生成路徑的第一步叫作beginPath()。本質上,路徑是由不少子路徑構成,這些子路徑都是在一個列表中,全部的子路徑(線、矩形、等等)構成圖形。而每次這個方法調用以後,列表清空重置,而後咱們就能夠從新繪製新的圖形。
注意:當前路徑爲空,即調用beginPath()以後,或者canvas剛建的時候,第一條路徑構造命令一般被視爲是moveTo(),不管最後的是什麼。出於這個緣由,你幾乎老是要在設置路徑以後專門指定你的起始位置。
第二步就是調用函數指定繪製路徑,等下回簡單介紹。
第三,就是閉合路徑closePath(),不是必需的。這個方法會經過繪製一條從當前點到開始點的直線來閉合圖形。若是圖形是已經閉合了的,即當前點爲開始點,該函數什麼也不作。
注意:當你調用fill()函數時,全部沒有閉合的形狀都會自動閉合,因此你不須要調用closePath()函數。而這並非stroke()所具備的。
繪製一個三角形
例如,繪製三角形的代碼以下:
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext){
var 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()繪製一個不連續的路徑。看一下下面的笑面例子。我將用到moveTo()方法(紅線處)的地方標記了。
你能夠嘗試一下,使用下邊的代碼片。只須要將其複製到以前的draw()函數便可。
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext){
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.arc(75,75,50,0,Math.PI*2,true); // 繪製
ctx.moveTo(110,75);
ctx.arc(75,75,35,0,Math.PI,false); // 口(順時針)
ctx.moveTo(65,65);
ctx.arc(60,65,5,0,Math.PI*2,true); // 左眼
ctx.moveTo(95,65);
ctx.arc(90,65,5,0,Math.PI*2,true); // 右眼
ctx.stroke();
}
}
結果以下:
若是你想看到連續的線,你能夠移除調用的moveTo()。
注意:須要學習更過關於arc()函數的,請看下面的{{anch("Arcs")}}
線
繪製直線,須要用到的方法lineTo();
lineTo(x, y)
繪製一條從當前位置到指定x以及y位置的直線。
該方法有兩個參數:x以及y ,表明座標系中直線結束的點。開始點和以前的繪製路徑有關,以前路徑的結束點就是接下來的開始點,等等。。。開始點也能夠經過moveTo()函數改變。
下面的例子繪製兩個三角形,一個是填充的,另外一個是描邊的。
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext){
var ctx = canvas.getContext('2d');
// 填充三角形
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();
}
}
這裏從調用beginPath()函數準備繪製一個新的形狀路徑開始。而後使用moveTo()函數移動到目標位置上。而後下面,兩條線段繪製後構成三角形的兩條邊。
你會注意到填充與描邊三角形步驟有所不一樣。正如上面所提到的,由於路徑使用填充(filled)時,路徑自動閉合,使用描邊(stroked)則不會閉合路徑。若是沒有添加閉合路徑closePath()到描述三角形函數中,則只繪製了兩條線段,並非一個完整的三角形。
圓弧(Arcs)
繪製圓弧或者圓,咱們使用arc()方法。固然可使用arcTo(),不過這個的現實並非那麼的可靠,咱們不用它。
arc(x, y, radius, startAngle, endAngle, anticlockwise)
繪製圓弧
該方法有五個參數: x,y爲繪製圓弧所在圓上的圓心座標。radius爲半徑。startAngle以及engAngle參數用弧度定義了開始以及結束的弧度。這些都是以x軸爲基準。參數anticlockwise 爲一個布爾值。爲true時,是逆時針方向,不然順時針方向。
注意:arc()函數中的角度單位是弧度,不是度數。角度與弧度的js表達式:radians=(Math.PI/180)*degrees。
下面的例子比上面的要複雜一下,下面繪製了12個不一樣的角度以及填充的圓弧。
下面兩個for循環,生成圓弧的行列(x,y)座標。每一段圓弧的開始都調用beginPath()。代碼中,每一個圓弧的參數都是可變的,實際生活中,咱們並不須要這樣作。
x,y座標是可變的。半徑(radius)和開始角度(startAngle)都是固定的。結束角度(endAngle)在第一列開始時是180度(半圓)而後每列增長90度。最後一列造成一個完整的圓。
clockwise 語句做用於第1、三行是順時針的圓弧,anticlockwise做用於2、四行爲逆時針圓弧。if語句讓1、二行描邊圓弧,下面兩行填充路徑。
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext){
var ctx = canvas.getContext('2d');
for(var i=0;i<4;i++){
for(var j=0;j<3;j++){
ctx.beginPath();
var x = 25+j*50; // x coordinate
var y = 25+i*50; // y coordinate
var radius = 20; // 圓弧半徑
var startAngle = 0; // 開始點
var endAngle = Math.PI+(Math.PI*j)/2; // 結束點
var anticlockwise = i%2==0 ? false : true; // 順時針或逆時針
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);
if (i>1){
ctx.fill();
} else {
ctx.stroke();
}
}
}
}
}
貝塞爾(bezier)以及二次貝塞爾
下一個十分有用的路徑類型就是 貝塞爾曲線。二次以及三次貝塞爾曲線都十分有用,通常用來繪製複雜有規律的圖形。
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 Illustrators這樣的矢量軟件,咱們所繪製的曲線沒有直接的視覺反饋給咱們。這讓繪製複雜的圖形十分的困難。在下面的例子中,咱們會繪製一些簡單有規律的圖形,若是你有時間,以及更多的耐心不少複雜的圖形你均可以繪製出來。
二次貝塞爾曲線
這個例子使用多個貝塞爾曲線來渲染對話框。
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
// 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();
}
}
三次貝塞爾曲線
這個例子使用貝塞爾曲線繪製心形。
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext){
var ctx = canvas.getContext('2d');
// Quadratric 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();
}
}
矩形
直接在畫布上繪製矩形的三個額外方法,正如咱們開始所見的 {{anch("Drawing rectangles")}},一樣,也有rect()方法,將一個矩形路徑增長到當前路徑上。
rect(x, y, width, height)
繪製一個左上角座標爲(x,y),寬高爲width以及height的矩形。
當該方法執行的時候,moveTo()方法自動設置座標參數(0,0)。也就是說,當前筆觸自動重置會默認座標。
組合使用
目前爲止,每個例子中的每一個圖形都只用到一種類型的路徑。然而,繪製一個圖形並無限制使用數量以及類型。因此在最後的一個例子裏,讓咱們組合使用全部的路徑函數來重現一組著名的遊戲人物。
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext){
var ctx = 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);
ctx.lineTo(31,37);
ctx.fill();
for(var 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();
}
}
// 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.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();
}
結果畫面以下:
咱們不會很詳細地講解上面的代碼,由於事實上這很容易理解。重要的一點是繪製上下文中使用到了fillStyle屬性,以及封裝函數(例子中的 roundedRect())。使用封裝函數對於減小代碼量以及複雜度十分有用。
在稍後的課程裏,咱們會回頭再看看fillStyle樣式的更多細節。這章節中,咱們所作的關於fillStyle樣式僅是改變填充顏色,由默認的黑色到白色,而後又是黑色。