1. 程式人生 > >【canvas學習筆記二】繪製圖形

【canvas學習筆記二】繪製圖形

上一篇我們已經講述了canvas的基本用法,學會了構建canvas環境。現在我們就來學習繪製一些基本圖形。

座標

canvas的座標原點在左上角,從左到右X軸座標增加,從上到下Y軸座標增加。座標的一個單元是1畫素。示意如下:
image

矩形

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);
  }
}

效果如下:

image

最外面那個黑色的矩形就是第一行程式碼ctx.fillRect(25, 25, 100, 100)畫的填充矩形。中間的透明矩形就是第二行程式碼ctx.clearRect(45, 45, 60, 60)清除的矩形,再中間的描邊矩形就是第三行程式碼ctx.strokeRect(50, 50, 50, 50)畫的。

路徑

繪製路徑的一般步驟如下:

  1. 創造路徑
  2. 繪製路徑
  3. 閉合路徑
  4. 填充路徑或給路徑描邊

繪製的程式碼如下:

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();
      }

效果如下:

image

再來畫個三角形:

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();
  }

效果:
image

矩形******

同樣的,路徑方法裡也有繪製矩形的方法。

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度。第一行和第三行都是順時針,第二行和第四行都是逆時針。第一行和第二行描邊,第三行和第四行填充。
效果是這樣的:
image

第二個圓弧方法比較複雜,效果難以控制,它其實是畫一段帶直線的圓弧。我儘量把它說明白。

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);

效果如圖:
image
藍色方塊的左上角座標是moveTo的座標,右下方的紅色方塊的左上角座標是第一個控制點的座標,左上方的紅色方塊的左上角座標是第二個控制點的座標。
注意,藍色方塊和圓弧之間是有一小段直線的。
注意,這個方法的開始是必須用moveTo方法來指定起始點的,也就是藍色方塊的位置,否則畫不出來。
這個方法難以控制,所以通常不去用它。

貝塞爾和二次曲線

現在我要說兩個更難以控制的繪圖API了,二次貝塞爾曲線和三次貝塞爾曲線。

quadraticCurveTo(cp1x, cp1y, x, y)

bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)

上面的是二次曲線,下面的是三次曲線。下面用一張圖來說明這兩個曲線方法。
image
這兩個方法的區別就是一個只有一個控制點(圖中紅點),一個有兩個控制點。
引數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();
  }
}

效果:
image
三次曲線的例子我就不說了。反正大家通常肯定不會用這兩個方法去繪製圖形的。

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);
  }
}

在這個例子中,建立了一個儲存了矩形路徑的物件,和一個儲存了圓的路徑物件,結果如下:
image
所有上面說過的路徑繪製方法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);