canvas核心技術-如何繪製圖形

這篇學習和回顧canvas系列筆記的第二篇,完整筆記詳見:canvas核心技術javascript

經過上一篇canvas核心技術-如何繪製線段的學習,咱們知道了如何去繪製線段。不少的線段的拼接就組成了圖形了,好比常見的三角形,矩形,圓形等。java

常見圖形的繪製能夠查看個人在線示例:canvas shapegit

示例項目倉庫地址:canvas demogithub

圖形

三角形

先來看看如何繪製一個三角形。三角形就是由三條邊組成,咱們能夠理解爲三個線段組成。肯定了三角形的三個頂點的座標位置,而後用線鏈接起來。canvas

let point1 = [100, 30]; //頂底1
let point2 = [50, 100]; //頂點2
let point3 = [180, 120]; //頂點3
ctx.beginPath(); //開始一段新路徑
ctx.moveTo(point1[0], point1[1]); //移動起點到頂點1
ctx.lineTo(point2[0], point2[1]); //鏈接頂點1與頂點2
ctx.lineTo(point3[0], point3[1]); //鏈接頂點2與頂點3
ctx.stroke(); //描邊
//繪製頂點座標顯示出來
ctx.textAlign='center'; //繪製文本水平居中
ctx.fillText(`(${point1[0]},${point1[1]})`, point1[0], point1[1]-10); //繪製頂點1文本
ctx.fillText(`(${point2[0]},${point2[1]})`, point2[0]-25, point2[1]+5); //繪製頂點2文本
ctx.fillText(`(${point3[0]},${point3[1]})`, point3[0]+30, point3[1]+5); //繪製頂點3文本
複製代碼

從圖能夠看到,咱們還有一條邊沒有鏈接起來,這是由於咱們只顯示的鏈接了2個頂點。要想把第三條邊也鏈接起來,咱們有2種方式。第一種方式是,咱們顯示的鏈接頂點3與頂點1瀏覽器

//第一種方式,顯示的鏈接頂點3於頂點1
ctx.lineTo(point1[0], point1[1]);
複製代碼

第二種方式是,咱們調用ctx.closePath()來按canvas自動幫咱們鏈接未關閉的路徑。bash

//第二種方式,調用ctx.closePath()
ctx.closePath();
複製代碼

不管哪種均可以實現咱們想要三角形。其中第二種方式會用的比較多,由於它會幫咱們自動關閉當前路徑,也就是使當前路徑造成一個閉合的路徑,這個在填充時是很是有用的,下面會說的。最終,咱們獲得三角形圖形以下函數

四邊形

經過三個頂點,咱們能夠繪製一個三角形,那麼經過四個點,咱們固然能夠繪製出四邊形,咱們照例來經過四個點來繪製一個矩形。post

let point1 = [80, 30]; //p1
let point2 = [180, 30]; //p2
let point3 = [80, 110]; //p3
let point4 = [180, 110]; //p4
ctx.strokeStyle = 'green'; //設置描邊顏色爲綠色
ctx.beginPath(); //開始新的一段路徑
ctx.moveTo(point1[0], point1[1]); //移動起點到p1
ctx.lineTo(point2[0], point2[1]); //鏈接p1與p2
ctx.lineTo(point4[0], point4[1]); //鏈接p2與p4
ctx.lineTo(point3[0], point3[1]); //鏈接p4與p3
ctx.closePath(); //關閉當前路徑,隱士鏈接p3與p1
ctx.stroke(); //描邊
//繪製頂點
ctx.textAlign = 'center';
ctx.fillText('p1', point1[0] - 10, point1[1] - 10);
ctx.fillText('p2', point2[0] + 10, point2[1] - 10);
ctx.fillText('p3', point3[0] - 10, point3[1] + 10);
ctx.fillText('p4', point4[0] + 10, point4[1] + 10);
複製代碼

注意,咱們的順序是p1-->p2-->p4--P3,因爲矩形是一種特殊的四邊形,在canvas中提供了一種方法能夠快速建立一個矩形,若是知道了p1的座標和矩形的寬度和高度,那麼咱們就能夠肯定了其餘三個點的座標。學習

//快速建立矩形
ctx.rect(point1[0], point1[1], 100, 80);
複製代碼

在建立矩形,咱們老是使用ctx.rect(left,top,width,height),可是繪製非矩形的四邊形,仍是得按照每一個點去鏈接成線段來繪製。

圓與圓弧

圓形能夠看做是無數個很小的線段鏈接起來的,可是經過去定頂點來繪製圓形,顯然不現實。canvas中提供了一個專門繪製圓形的方法ctx.arc(left,top,radius,startAngle,endAngle,antiClockwise)。各個參數的順序意思是,圓心座標X值,圓心座標Y值,半徑,開始弧度,結束弧度,是否逆時針。經過指定startAngle=0endAngle=Math.PI*2,就能夠繪製一個完整的圓了。最後一個參數antiClockwise對於圖片的填充時會很是有用,後面講填充時會詳細說到。

let center = [100, 75]; //圓心座標
let radius = 50; //半徑
let startAngle = 0; //開始弧度值
let endAngle = Math.PI * 2; //結束弧度值,360度=Math.PI * 2
let antiClockwise = false; //是否逆時針
ctx.strokeStyle = 'blue'; //描邊顏色
ctx.lineWidth = 1;
ctx.arc(center[0], center[1], radius, startAngle, endAngle, antiClockwise);
ctx.stroke(); //將圓形描邊繪製出來
//繪製出圓心和半徑示意圖,讀者能夠忽略下半部代碼
ctx.beginPath();
ctx.fillStyle = 'red';
ctx.arc(center[0], center[1], 2, startAngle, endAngle, antiClockwise);
ctx.fill();
ctx.beginPath();
ctx.moveTo(center[0], center[1]);
ctx.lineTo(center[0] + radius, center[1]);
ctx.stroke();
ctx.fillStyle = 'blue';
ctx.font = '24px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('r', center[0] + radius / 2, center[1] - 10);
複製代碼

咱們還能夠改變起始和結束弧度的值,來繪製不一樣角度的弧形。好比八分之一圓弧,四分之圓弧,半圓弧等。

let center = [50, 75]; //圓心座標
let radius = 20; //半徑
let startAngle = 0; //起始弧度爲0
let antiClockwise = false; //是否逆時針
let angles = [1 / 8, 1 / 4, 1 / 2, 3 / 4]; //弧度長度
let colors = ['red', 'blue', 'green', 'orange']; //描邊顏色
for (let [i, angle] of angles.entries()) {
  let endAngle = Math.PI * 2 * angle; //計算結束弧度
  ctx.strokeStyle = colors[i]; //設置描邊顏色
  ctx.beginPath(); //開始新的路徑
  ctx.arc(center[0] + i * radius * 3, center[1], radius, startAngle, endAngle, antiClockwise); //繪製圓弧
  ctx.stroke(); //描邊
}
複製代碼

任意多邊形

上面說的都是一些比較簡單和常見的圖形,咱們如何能夠繪製任意多邊形,好比五邊形,六邊形,八邊形等。其實,在繪製四邊形的時候就說過了,能夠經過肯定頂點座標,而後把這些頂點按照必定順序鏈接起來就能夠了。下面,來實現一個通用的多邊形的繪製方法。

class Polygon {
  constructor(ctx, points) {
    this.ctx = ctx;
    this.points = points;
  }
  draw() {
    if (!this.ctx instanceof CanvasRenderingContext2D) {
      throw new Error('Polygon#ctx must be an CanvasRenderingContext2D instance');
    }
    if (!Array.isArray(this.points)) {
      throw new Error('Polygon#points must be an Array');
    }
    if (!this.points.length) {
      return;
    }
    let firstPoint = this.points[0];
    let restPoint = this.points.slice(1);
    ctx.beginPath();
    ctx.moveTo(firstPoint[0], firstPoint[1]);
    for (let point of restPoint) {
      ctx.lineTo(point[0], point[1]);
    }
    ctx.closePath();
  }
}
複製代碼

經過實例化這個Polygon,並傳入多邊形的頂點座標,咱們就能夠繪製出不一樣的多邊形。例以下面的代碼,分別繪製了五邊形,六邊形。

//繪製五邊形
let points = [[30, 40], [80, 40], [100, 80], [55, 120], [10, 80]];
let pentagon = new Polygon(ctx, points);
ctx.strokeStyle = 'blue';
pentagon.draw();
ctx.stroke();

//繪製六邊形
points = [[160, 40], [210, 40], [230, 80], [210, 120], [160, 120], [140, 80]];
let hexagon = new Polygon(ctx, points);
ctx.strokeStyle = 'green';
hexagon.draw();
ctx.stroke();
複製代碼

填充

上面,咱們都是用描邊把圖形繪製出來,還有一種用的比較多的就是填充了。填充就是用特定的顏色把圖形包圍的區域塗滿。

let point1 = [100, 30]; //頂底1
let point2 = [50, 100]; //頂點2
let point3 = [180, 120]; //頂點3
ctx.strokeStyle = 'red'; //用紅色描邊
ctx.fillStyle = 'yellow'; //用黃色填充
ctx.lineWidth = 2; //設置線段寬度爲2
ctx.beginPath(); //開始一段新路徑
ctx.moveTo(point1[0], point1[1]); //移動起點到頂點1
ctx.lineTo(point2[0], point2[1]); //鏈接頂點1與頂點2
ctx.lineTo(point3[0], point3[1]); //鏈接頂點2與頂點3
ctx.closePath(); //關閉當前路徑
ctx.stroke(); //描邊
ctx.fill(); //填充
複製代碼

須要注意的是,若是當前路徑沒有關閉,那麼會先默認關閉當前路徑,而後在進行填充 ,以下,咱們把ctx.closePath()註釋掉。

let point1 = [100, 30]; //頂底1
let point2 = [50, 100]; //頂點2
let point3 = [180, 120]; //頂點3
ctx.strokeStyle = 'red'; //用紅色描邊
ctx.fillStyle = 'yellow'; //用黃色填充
ctx.lineWidth = 2; //設置線段寬度爲2
ctx.beginPath(); //開始一段新路徑
ctx.moveTo(point1[0], point1[1]); //移動起點到頂點1
ctx.lineTo(point2[0], point2[1]); //鏈接頂點1與頂點2
ctx.lineTo(point3[0], point3[1]); //鏈接頂點2與頂點3
// ctx.closePath(); //關閉當前路徑
ctx.stroke(); //描邊
ctx.fill(); //填充
複製代碼

若是當前路徑是循環的,或者是包含多個相交的子路徑,那麼canvas何如進行填充呢?好比下面這樣的,爲什麼在填充時,中間這一塊沒有被填充?

let point1 = [100, 30];
let point2 = [50, 100];
let point3 = [180, 120];
let point4 = [50, 60];
let point5 = [160, 80];
let point6 = [70, 120];
ctx.strokeStyle = 'red';
ctx.fillStyle = 'yellow';
ctx.lineWidth = 2;
ctx.beginPath(); //開始一段新路徑
//繪製三角形1, 順序:p1--p2--p3--p1
ctx.moveTo(point1[0], point1[1]);
ctx.lineTo(point2[0], point2[1]);
ctx.lineTo(point3[0], point3[1]);
ctx.lineTo(point1[0], point1[1]);
//繪製三角形2,順序:p4--p5--p6--p4
ctx.moveTo(point4[0], point4[1]);
ctx.lineTo(point5[0], point5[1]);
ctx.lineTo(point6[0], point6[1]);
ctx.lineTo(point4[0], point4[1]);
ctx.stroke(); //描邊
ctx.fill(); //填充
複製代碼

咱們來具體研究一下fill函數,查看MDN上的解釋,

The CanvasRenderingContext2D.fill() method of the Canvas 2D API fills the current or given path with the current fill style using the non-zero or even-odd winding rule

void ctx.fill([fillRule]);
void ctx.fill(path[, fillRule]);
複製代碼

fillRule參數是可選的,可取值爲nonzero,evenodd。也就是說,fill函數能夠給當前路徑或者給定的路徑,使用非零環繞規則或者奇偶規規則來填充。path 參數是一個Path2D對象,是一個給定的路徑,canvas中默認的是當前路徑,這個參數並非全部的瀏覽器都支持,目前看,還有IE系列和移動設備上都沒有很好的支持,就很少說了,具體能夠查看Path2D

非零環繞規則

對於路徑中的任意給定區域,從該區域內部畫出一條足夠長的線段,使此線段的終點徹底落在路徑範圍以外。接下來,將計數器初始化爲0,而後,每當這條線段與路徑上的直線或者曲線相交時,就改變計數器的值。若是是與路徑的順時針部分相交,則加1,若是是與路徑的逆時針部分相交,則減1。最後,如計數器的值不爲0,則此區域就在路徑裏面,調用fill時,該區域被填充。若是計數器的最終值爲0,則此區域就不在路徑裏面,調用fill時,該區域就不被填充。canvas的fill默認使用的就是這種非零環繞規則。

再來看看上圖,爲什麼中間交叉區域沒有被填充。咱們繪製了2個三角形,第一繪製順序是p1-->p2-->p3-->p1,第二個繪製順序是p4-->p5-->p6-->p4 。能夠看到第一個三角形在繪製是逆時針方向的,第二個三角形繪製是順時針方向的,中間相交區域的計數器最終值就爲0了,因此不該該包含在這個路徑中。

非零環繞規則演示能夠查看個人示例:非零環繞示例

奇偶規則

跟非零環繞規則相似,都是從任意區域畫出一條足夠長的線,使此線段的終點徹底落在路徑範圍以外。若是這個線段與路徑相交的個數位奇數,則此區域包含在路徑中,若是爲偶數,則表示此區域不包含在路徑中。

例如,咱們把上面的例子改下,繪製第二個三角形的順序改爲逆時針p4-->p6-->p5--P4,而後分別用非零環繞規則奇偶規則來填充,看看效果。

//繪製三角形2,注意順序變了:p4-p6-p5-p4
ctx.moveTo(point4[0], point4[1]);
ctx.lineTo(point6[0], point6[1]);
ctx.lineTo(point5[0], point5[1]);
ctx.lineTo(point4[0], point4[1]);
ctx.stroke(); //描邊
ctx.fill(); //填充, 默認就是非零環繞規則
複製代碼

上面兩個三角形的順序都是逆時針,因此按照非零環繞規則,像個三角形的相交區域的計數器的最終值爲-2,不爲0,則包含在路徑中改,被填充了。

一樣的順序,咱們在改用奇偶規則來填充。

ctx.fill('evenodd'); //填充, 改用奇偶規則
複製代碼

小結

這篇咱們主要學習了canvas中如何繪製圖形,好比常見的三角形,四邊形,圓心,以及任意多邊形。在繪製圖形時,有些好比矩形,圓形等canvas已經提供了內置的函數,ctx.rect()ctx.arc能夠直接繪製,可是對於任意多邊形,咱們則須要本身逐線段的繪製。

在繪製路徑時,是有順序的。理解canvas中路徑,和當前繪製的順序,就能夠很好的理解了canvas中填充規則了。canvas中填充有非零環繞規則奇偶規則。對於一樣的路徑,不一樣的規則可能會產生不一樣的填充區域,是使用時,注意路徑順序就行了。

相關文章
相關標籤/搜索