【canvas學習筆記二】繪製圖形
上一篇我們已經講述了canvas的基本用法,學會了構建canvas環境。現在我們就來學習繪製一些基本圖形。
座標
canvas的座標原點在左上角,從左到右X軸座標增加,從上到下Y軸座標增加。座標的一個單元是1畫素。示意如下:
矩形
canvas可以繪製的多邊形只有矩形,其他都要通過線段拼接而成。
繪製矩形有三個API:
fillRect(x, y, width, height) 繪製一個填充的矩形。 strokeRect(x, y, width, height) 繪製一個只有描邊的矩形。 clearRect(x, y, width, height) 清除特定的矩形區域,使之變成透明。
引數說明:
x,y是矩形左上角的座標,width和height是矩形的寬和高。
程式碼示例:
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.fillRect(25, 25, 100, 100);
ctx.clearRect(45, 45, 60, 60);
ctx.strokeRect(50, 50, 50, 50);
}
}
效果如下:
最外面那個黑色的矩形就是第一行程式碼ctx.fillRect(25, 25, 100, 100)畫的填充矩形。中間的透明矩形就是第二行程式碼ctx.clearRect(45, 45, 60, 60)清除的矩形,再中間的描邊矩形就是第三行程式碼ctx.strokeRect(50, 50, 50, 50)畫的。
路徑
繪製路徑的一般步驟如下:
- 創造路徑
- 繪製路徑
- 閉合路徑
- 填充路徑或給路徑描邊
繪製的程式碼如下:
ctx.beginPath(); // 創造路徑
// 路徑繪製,先省略,後面再路徑繪製的方法
ctx.closePath(); // 閉合路徑
ctx.stroke(); // 描邊
// ctx.fill(); // 換成這行命令就是填充
在這個一般步驟中,只有閉合路徑步驟可以省略,其他步驟都是不可省略的。如果不需要畫閉合的圖形,可以不閉合路徑。
接下來講述繪製路徑的方法。
移動畫筆
moveTo(x, y)這個方法可以移動畫筆的座標,將畫筆移動到座標(x, y)的位置。
用beginPath()創造路徑時,要先用moveTo(x, y)確定起始位置,除非是圓弧之類不需要初始點的路徑繪製方法。
線段
lineTo(x, y)方法繪製一條線段,x, y指定了線段末尾的座標。配合moveTo(x, y)方法就可以畫出線段。
示例:
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(0,0)
ctx.lineTo(50,50);
ctx.lineTo(100,50);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(75,0)
ctx.lineTo(70,50);
ctx.lineTo(150,50);
ctx.closePath();
ctx.stroke();
}
效果如下:
再來畫個三角形:
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();
}
效果:
矩形******
同樣的,路徑方法裡也有繪製矩形的方法。
rect(x, y, width, height)
x,y是矩形左上角座標,width是矩形的寬,height是矩形的高。
這個方法和前面說的矩形不同在於,這個方法是路徑繪製方法,要配合beginPath()和stroke()之類的方法來使用的。要先建立路徑,然後用這個方法繪製矩形,再stroke()或fill(),否則畫不出來。
圓弧
繪製圓弧有兩個方法:
arc(x, y, radius, startAngle, endAngle, anticlockwise)
arcTo(x1, y1, x2, y2, radius)
第一個方法中的x, y引數指定了圓弧的圓心座標。
radius引數是圓弧的半徑長度。
startAngle引數是圓弧起始的弧度,endAngle是圓弧結束的弧度,單位是弧度,不是度。
anticlockwise是個布林值,設定圓弧是順時針畫還是逆時針畫,當anticlockwise為true是逆時針,為false是順時針,這個值預設的時候預設是順時針。
Tips:
角度的單位是弧度,可以用這個表示式把度數轉換成弧度:
radians = (Math.PI/180)*degrees
下面來看一個有趣的例子:
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; // 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; // clockwise or anticlockwise
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);
if (i > 1) {
ctx.fill();
} else {
ctx.stroke();
}
}
}
}
}
這段程式碼總共畫了四行三列的圓弧。相鄰的圓弧圓心X軸和Y軸都相距50畫素,圓弧的半徑都是20畫素,每列圓弧的弧度相差90度。第一行和第三行都是順時針,第二行和第四行都是逆時針。第一行和第二行描邊,第三行和第四行填充。
效果是這樣的:
第二個圓弧方法比較複雜,效果難以控制,它其實是畫一段帶直線的圓弧。我儘量把它說明白。
arcTo(x1, y1, x2, y2, radius)
引數x1, y1, x2, y2分別是兩個控制點的座標,radius是圓弧的半徑長度。
下面用一個例子來說明控制點的意義:
ctx.beginPath();
ctx.moveTo(150, 20);
ctx.arcTo(150, 100, 50, 20, 30);
ctx.stroke();
ctx.fillStyle = 'blue';
// base point
ctx.fillRect(150, 20, 10, 10);
ctx.fillStyle = 'red';
// control point one
ctx.fillRect(150, 100, 10, 10);
// control point two
ctx.fillRect(50, 20, 10, 10);
效果如圖:
藍色方塊的左上角座標是moveTo的座標,右下方的紅色方塊的左上角座標是第一個控制點的座標,左上方的紅色方塊的左上角座標是第二個控制點的座標。
注意,藍色方塊和圓弧之間是有一小段直線的。
注意,這個方法的開始是必須用moveTo方法來指定起始點的,也就是藍色方塊的位置,否則畫不出來。
這個方法難以控制,所以通常不去用它。
貝塞爾和二次曲線
現在我要說兩個更難以控制的繪圖API了,二次貝塞爾曲線和三次貝塞爾曲線。
quadraticCurveTo(cp1x, cp1y, x, y)
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
上面的是二次曲線,下面的是三次曲線。下面用一張圖來說明這兩個曲線方法。
這兩個方法的區別就是一個只有一個控制點(圖中紅點),一個有兩個控制點。
引數cp1x, cp1y, cp2x, cp2y是控制點的座標,x, y是曲線的末端的座標值,也就是圖中的藍點中的其中一個。
因為不是在Illustrator等圖形編輯軟體裡,不能視覺化地控制點,所以用這個兩個方法繪製圖形相當需要耐心。
舉個二次曲線畫圖的例子吧:
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();
}
}
效果:
三次曲線的例子我就不說了。反正大家通常肯定不會用這兩個方法去繪製圖形的。
2D路徑物件
實際繪圖過程中,常常需要繪製很多路徑,其中很多可能是重複的。2D路徑物件的作用就是儲存重複使用的路徑,提高效能,簡化程式碼,新版瀏覽器都支援。
我先說一下初始化2D路徑物件的方法,有三種:
new Path2D(); // empty path object
new Path2D(path); // copy from another Path2D object
new Path2D(d); // path from SVG path data
建構函式可以不含引數,這樣建立的就是一個空的路徑物件,同樣也可以傳一個2D路徑物件作引數,這樣就拷貝了一個2D路徑物件,還可以傳一段SVG路徑資料,比如這樣:var p = new Path2D('M10 10 h 80 v 80 h -80 Z');
還是用例子來說明Path2D這個物件比較清楚:
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
var rectangle = new Path2D();
rectangle.rect(10, 10, 50, 50);
var circle = new Path2D();
circle.moveTo(125, 35);
circle.arc(100, 35, 25, 0, 2 * Math.PI);
ctx.stroke(rectangle);
ctx.fill(circle);
}
}
在這個例子中,建立了一個儲存了矩形路徑的物件,和一個儲存了圓的路徑物件,結果如下:
所有上面說過的路徑繪製方法Path2D這個物件都能使用。
另外Path2D這個物件還有一個拼接路徑的方法:
Path2D.addPath(path [, transform])
Adds a path to the current path with an optional transformation matrix.
把一段路徑傳進去就可以將這兩段路徑拼接起來,帶有一個可選引數,是一個SVG變換矩陣。
舉個栗子:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// Create a new path with a rect
var p1 = new Path2D();
p1.rect(0, 0, 100, 100);
// Create another path with a rect
var p2 = new Path2D();
p2.rect(0, 0, 100, 100);
// Create transformation matrix that moves vertically 300 points to the right
var m = document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGMatrix();
m.a = 1; m.b = 0;
m.c = 0; m.d = 1;
m.e = 300; m.f = 0;
// add the second path to the first path
p1.addPath(p2, m);
// Finally, fill the first path onto the canvas
ctx.fill(p1);