Canvas之路徑與繪製

前言

canvas的繪製系統是基於路徑的,即先用代碼定義路徑(看不見的),再描邊或者填充
不過canvas中也有兩個當即繪製圖形的API:strokeReact()與fillReact()(fillText()與strokeText()也能直接繪製,不過繪製的是文字)php

繪製複雜圖形的方法都是基於路徑html

本文總結了我對canvas路徑與繪製實踐,主要內容以下html5

  • 路徑與子路徑概念
  • 經常使用API
  • canvas基於路徑的繪製步驟
    • Demo1:使用兩次beginPath(),繪製邊顏色不一樣的直角
    • Demo2:不使用beginPath()會發生什麼
    • Demo3:瀏覽器自動填充與closePath()
    • Demo4:畫一個三條邊顏色不同的填充三角形
  • 填充路徑的「非零環繞規則」
  • 結語
  • 參考資料

Demo我會放在CodePen之中,能夠直接打開調試、預覽canvas

路徑與子路徑概念

在某一時刻,canvas之中只能有一條路徑存在,Canvas規範將其稱爲「當前路徑「(current path)。然而,這條路徑卻能夠包含許多子路徑(subpath)。而子路徑,又是由兩個或更多的點組成的。
——《HTML5 Canvas核心技術》瀏覽器

在W3C文檔上找到了相關描述:W3C:11 Drawing paths to the canvasbash

The context always has a current default path. There is only one current path, it is not part of the drawing state. The current path is a path, as described above.ide

指出current path 即其文檔中描述的path學習

Each object implementing the CanvasPathMethods interface has a path. A path has a list of zero or more subpaths. Each subpath consists of a list of one or more points, connected by straight or curved lines, and a flag indicating whether the subpath is closed or not.ui

連接在此:W3C:5 Building paths
爲了方便描述不易混淆,此文就叫作當前路徑
當前路徑我用圖這樣理解👇
this

即當前路徑 = 子路徑3 + 子路徑2 + 子路徑1

經常使用API

在知道路徑與子路徑的概念後咱們看看有哪些經常使用的API

API或屬性 說明
beginPath() starts a new path by emptying the list of sub-paths.
closePath() attempts to add a straight line from the current point to the start of the current sub-path. If the shape has already been closed or has only one point, this function does nothing.
stroke() strokes (outlines) the current or given path with the current stroke style.
fill() fills the current or given path with the current fillStyle.
moveTo(x, y) begins a new sub-path at the point specified by the given (x, y) coordinates.
lineTo(x, y) adds a straight line to the current sub-path by connecting the sub-path's last point to the specified (x, y) coordinates.
strokeStyle specifies the color, gradient, or pattern to use for the strokes (outlines) around shapes. The default is #000 (black).
lineWidth sets the thickness of lines.
fillStyle specifies the color, gradient, or pattern to use inside shapes. The default style is #000 (black).

API說明來自MDN
由於本文總結的主要是路徑與繪製,因此只列舉了一些經常使用的API,更多的API諸如arc(),quadraticCurveTo()等可在MDN:Drawing shapes with canvas 發現

這裏要注意的是beginPath()moveTo()
beginPath()會清空掉原來的子路徑,若是不清空,在屢次調用stroke()時,後面調用的stroke()會再次繪製原先的子路徑
moveTo()會開啓新的子路徑

canvas基於路徑的繪製步驟

  1. 使用beginPath()開始繪製路徑或清空子路徑
  2. 使用lineTo()、moveTo()等API繪製路徑
  3. 根據須要是否使用closePath()閉合路徑
  4. 根據須要使用stroke()或者fill()等
  5. 根據須要是否使用beginPath()清除當前子路徑,開始新的繪製 後面以Demo的形式舉例

Demo1:使用兩次beginPath(),繪製邊顏色不一樣的角

CodePen打開

ctx.beginPath();
ctx.strokeStyle = "pink";
ctx.lineWidth = 3;
ctx.moveTo(30, 10); //開始新的子路徑
ctx.lineTo(30, 200); //繪製子路徑
ctx.stroke();

ctx.beginPath(); //清空子路徑
ctx.strokeStyle = "green";
ctx.moveTo(30, 200);//開始新的子路徑
ctx.lineTo(230, 200);//繪製子路徑
ctx.stroke();
複製代碼

第二次調用beginPath()時清空了子路徑,並開始畫綠線。若是不清空子路徑會發生什麼呢,看Demo2

Demo2:不使用beginPath()會發生什麼

CodePen打開

ctx.beginPath();
ctx.strokeStyle = "pink";
ctx.lineWidth = 3;
ctx.moveTo(30, 10);
ctx.lineTo(30, 200); //繪製子路徑
ctx.stroke();

//ctx.beginPath(); 
ctx.strokeStyle = "green";
ctx.moveTo(30, 200);
ctx.lineTo(230, 200);//繪製子路徑
ctx.stroke();
複製代碼

能夠看到,粉色線不見了,實際上是被綠線覆蓋了。由於原先的子路徑並無被清除,因此第二次調用stroke()時,圖中有兩條子路徑,故繪製時把原來的粉線覆蓋。
到這裏大概能夠理解路徑與子路徑及beginPath()的做用了吧

Demo3:瀏覽器自動填充與closePath()

路徑分爲封閉路徑開放路徑,可是不管是封閉路徑仍是開放路徑均可以進行填充,當填充開放路徑時,瀏覽器會將其看成封閉路徑來填充
瀏覽器自動填充Demo👇:

CodePen打開

ctx.beginPath();
ctx.strokeStyle = "pink";
ctx.lineWidth = 5;
ctx.moveTo(30, 10);
ctx.lineTo(30, 200);
ctx.lineTo(230, 200);//繪製子路徑
// ctx.closePath();
ctx.stroke();
ctx.fill();
複製代碼

能夠看到沒有粉色斜邊被stroke(),但fill()時瀏覽器進行了自動填充
若是是closePath()或者手動lineTo()就會有對應的邊

CodePen打開

ctx.beginPath();
ctx.strokeStyle = "pink";
ctx.lineWidth = 5;
ctx.moveTo(30, 10);
ctx.lineTo(30, 200);
ctx.lineTo(230, 200);//繪製子路徑
// ctx.closePath()或者lineTo()
// ctx.closePath()
ctx.lineTo(30,10);
ctx.stroke();
ctx.fill();
複製代碼

Demo4:畫一個三條邊顏色不同的填充三角形

經過上面實踐(Demo2),咱們發現不能在當前路徑中繪製不一樣顏色的邊,同時beginPath()會清空子路徑以重置當前路徑,因此咱們沒辦法在不覆蓋邊的狀況下一鼓作氣fill()一個三角形,須要調用四次beginPath()(三次用於畫邊,一次用於三角形路徑並填充;若是在能夠覆蓋邊的狀況下,三次beginPath()便可,在一次fill()時繪製邊,後面繪製邊時進行覆蓋)

CodePen打開

// 畫粉邊框
ctx.beginPath();
ctx.strokeStyle = "pink";
ctx.moveTo(30, 10);
ctx.lineTo(30, 200);
ctx.stroke();

//畫綠邊框
ctx.beginPath();
ctx.strokeStyle = "green";
ctx.moveTo(30, 200);
ctx.lineTo(210, 200);//繪製子路徑
ctx.stroke();

//畫藍邊框
ctx.beginPath();
ctx.strokeStyle = "blue";
ctx.moveTo(210, 200);
ctx.lineTo(30, 10);//繪製子路徑
ctx.stroke();

//三角形路徑繪製,並填充
ctx.beginPath();
ctx.fillStyle="yellow";
ctx.moveTo(30, 10);//開始一條子路徑
ctx.lineTo(30, 200);
ctx.lineTo(210, 200);
ctx.closePath();
ctx.fill();
複製代碼

填充路徑的「非零環繞規則」

若是當前路徑有鏈接造成多個閉環或者各個子路徑相交造成多個閉環,在進行fill()填充時,會使用」非零環繞規則「來判斷如何進行填充

如上圖,是填充外邊的環,仍是填充裏面的三角形?
如下是」非零環繞規則「的釋義

」非零環繞規則「是這麼來判斷有自我交叉狀況的路徑的:對於路徑中的任意給定區域,從該區域內部畫一條足夠長的線段,使此線段的終點徹底落在路徑範圍以外。接下來,將計數器初始化爲0,而後,每當這條線段與路徑上的直線或曲線相交時,就改變計數器的值。若是是與路徑的順時針部分相交,則加1,若是是與路徑的逆時針部分相交,則減一。若計數器的最終值不是0,那麼此區域就在路徑裏面,在調用fill()方法時,瀏覽器就會對其進行填充。若是最終值是0,那麼此區域就不在路徑內部,瀏覽器也就不會對其進行填充了 ——《HTML5 Canvas核心技術》

下面舉個例子,圖自Wikipedia

區域1向外畫箭頭,通過一個逆時針的路徑,計數器-1,再通過一個順時針的路徑,計數器+1,最終爲0,因此區域1不填充
區域2向外畫箭頭,通過一個順時針的路徑,計數器+1,又通過一個順時針的路徑,計數器+1,最終爲2,不爲0,因此區域2填充
最後只要是計數器不爲0便可進行填充
拿上面三角形的例子繼續舉例

因此均有填充,圖片及對應代碼以下

(注意fill()與stroke()的調用順序,若是fill()在後,會把stroke()後的部分描邊覆蓋掉)
CodePen打開

CodePen打開

結語

本文經過一些基礎的API講述了路徑與繪製,還有其餘一些諸如arc()等創造子路徑的API能夠去MDN查看。
若有不對歡迎交流學習✨

參考資料

HTML5 Canvas path tutorial
MDN:Drawing shapes with canvas
W3C:5 Building paths
W3C:11 Drawing paths to the canvas

相關文章
相關標籤/搜索