canvas的繪製系統是基於路徑
的,即先用代碼定義路徑(看不見的),再描邊或者填充
不過canvas中也有兩個當即繪製圖形的API:strokeReact()與fillReact()(fillText()與strokeText()也能直接繪製,不過繪製的是文字)php
繪製複雜圖形的方法都是基於路徑
的html
本文總結了我對canvas路徑與繪製實踐,主要內容以下html5
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或屬性 | 說明 |
---|---|
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()
會開啓新的子路徑
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
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()的做用了吧
路徑分爲封閉路徑
與開放路徑
,可是不管是封閉路徑仍是開放路徑均可以進行填充,當填充開放路徑
時,瀏覽器會將其看成封閉路徑來填充
瀏覽器自動填充Demo👇:
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()就會有對應的邊
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();
複製代碼
經過上面實踐(Demo2),咱們發現不能在當前路徑中繪製不一樣顏色的邊
,同時beginPath()會清空子路徑以重置當前路徑,因此咱們沒辦法在不覆蓋邊
的狀況下一鼓作氣fill()一個三角形,須要調用四次beginPath()(三次用於畫邊,一次用於三角形路徑並填充;若是在能夠覆蓋邊
的狀況下,三次beginPath()便可,在一次fill()時繪製邊,後面繪製邊時進行覆蓋)
// 畫粉邊框
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填充
本文經過一些基礎的API講述了路徑與繪製,還有其餘一些諸如arc()等創造子路徑的API能夠去MDN查看。
若有不對歡迎交流學習✨
HTML5 Canvas path tutorial
MDN:Drawing shapes with canvas
W3C:5 Building paths
W3C:11 Drawing paths to the canvas