在上一篇介紹Canvas座標系統的結尾處,我們使用了Canvavs繪製了一個網格。整個效果是由直線和文本構成。在這一節中,我們來看看如何使用Canvas繪製線段。
在Canvas中,線段也是路徑中的一種,被稱之爲線性路徑。在Canvas中繪製線性路徑主要用到moveTo(x,y)
、lineTo(x,y)
和stroke()
幾個方法。
先畫一條簡單的直線
Canvas畫一下直線非常的容易。衆所周之,兩點就能構成一條直線。使用兩個API就可以:moveTo()
告訴你把畫筆移到Canvas畫布中的某個位置(直線的起點),然後通過lineTo()
把畫筆移到另一個點。從而兩個點構成一條直線。
function drawScreen () {
ctx.moveTo(50, 10);
ctx.lineTo(350, 100);
}
但這樣在畫布看不到任何的線條。如果需要看到效果,還需要使用stroke()
方法:
function drawScreen () {
ctx.moveTo(50, 10);
ctx.lineTo(350, 100);
ctx.stroke();
}
是不是很簡單,通過這三個方法就可以繪製出一條線段。
moveTo(x,y)
:移動畫筆到指定的座標點(x,y)
,該點就是新的子路徑的起始點。該方法並不會從當前路徑中清除任何子路徑lineTo(x,y)
:使用直線連接當前端點和指定的座標點(x,y)
。stroke()
:沿着繪製路徑的座標點順序繪製直線
改變線段寬度
我們在實際繪製線段時,總是有粗細的情況發生。那麼在Canvas中可以通過lineWidth
來改變繪製線段的粗細。比如:
function drawScreen () {
ctx.lineWidth = 10; // 改變線的粗細
ctx.moveTo(50, 10); // 起始點
ctx.lineTo(350, 100); // 第二點(如果是一條直線的話,就是終點)
ctx.stroke();
}
lineWidth
主要用來定義繪製線條的寬度。默認值是1.0
,並且這個屬性必須大於0.0
。較寬的線條在路徑上居中,每邊各有線條寬的一半。
改變線段的顏色
既然能改爲線段的粗細,那必然能改變線段的顏色。在Canvas中可以通過strokeStyle
來改變線段的顏色:
function drawScreen () {
ctx.lineWidth = 10;
ctx.strokeStyle = '#f36';
ctx.moveTo(50, 10);
ctx.lineTo(350, 100);
ctx.stroke();
}
strokeStyle
主要用於設置畫筆繪製路徑的顏色、漸變和模式。該屬性的值可以是一個表示CSS顏色值的字符串。如果你的繪製需求比較複雜,該屬性的值還可以是一個CanvasGradient
對象或者CanvasPattern
對象。
也就是說,我們也可以繪製漸變色的線段:
function drawScreen () {
// 創建一個表示線性顏色漸變的CanvasGradient對象,
// 並設置該對象的作用區域就是線段所在的區域
var canvasGradient = ctx.createLinearGradient(50, 50, 250, 50);
//在offset爲0的位置(即起點位置)添加一個藍色的漸變
canvasGradient.addColorStop(0, "blue");
//在offset爲0.2的位置(線段左起20%的位置)添加一個綠色的漸變
canvasGradient.addColorStop(0.2, "green");
//在offset爲0的位置(即終點位置)添加一個紅色的漸變
canvasGradient.addColorStop(1, "red");
//將strokeStyle的屬性值設爲該CanvasGradient對象
ctx.strokeStyle = canvasGradient;
ctx.lineWidth = 10;
ctx.moveTo(50, 10);
ctx.lineTo(350, 100);
ctx.stroke();
}
CanvasGradient
接口表示描述漸變的不透明對象。通過 CanvasRenderingContext2D.createLinearGradient()
或
CanvasRenderingContext2D.createRadialGradient()
的返回值得到。
如此一來,是不是可以畫具有紋理的線段呢?思考一下,你就會有答案。
beginPath()和closePath()
前面也說過了,線段也是線性路徑中的一種。有開始也會有結束。其實在Canvas中具有兩個方法:beginPath()
和closePath()
。
beginPath()
:開始一個新的繪製路徑。每次繪製新的路徑之前記得調用該方法。它將重置內存中現有的路徑closePath()
:如果當前的繪製路徑是打開的,則關閉掉該繪製路徑。此外,調用該方法時,它會嘗試用直線邊接當前端點與起始端點來關閉路徑,但如果圖形已經關閉(比如先調用stroke()
)或者只有一個點,它會什麼都不做。
在Canvas中繪製路徑,最好加上beginPath()
和closePath()
。配合lineTo()
不同點,我們可以繪製不同的路徑。
function drawScreen () {
ctx.strokeStyle = '#f36';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(50, 10);
ctx.lineTo(150, 10);
ctx.lineTo(150,200);
ctx.lineTo(200,200);
ctx.lineTo(200,150);
ctx.stroke();
ctx.closePath();
}
把上面的代碼稍做修改:
function drawScreen () {
ctx.strokeStyle = '#f36';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(50, 10);
ctx.lineTo(150, 10);
ctx.lineTo(150,200);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.moveTo(200,200);
ctx.lineTo(200,150);
ctx.stroke();
ctx.closePath();
}
你在效果中可以看到,這個示例,我們是繪製了兩個路徑。
特別提醒:在繪製圖形路徑時,一定要先調用
beginPath()
。beginPath()
方法將會清空內存中之前的繪製路徑信息。如果不這樣做,對於繪製單個圖形可能沒什麼影響,但是在繪製多個圖形時(例如上面示例的兩條直線),將會導致路徑繪製或者顏色填充等操作出現任何意料之外的結果。
此外,對於closePath()
方法,初學者一定要稍加註意。在上面繪製折線的代碼示例中,我們先調用了stroke()
,再調用了closePath()
。其實在調用stroke()
方法時,折線就已經繪製好了,當前的繪製路徑也就被關閉掉了,所以再調用closePath()
方法時,它就不會使用直線連接當前端點和起始端點(也就是說,這裏的closePath()
是可有可無的,不過爲了保持良好的習慣,還是建議寫上)。如果我們交換一下stroke()
和closePath()
的調用順序,則情況完全不一樣了。由於closePath()
先調用,此時繪製路徑並沒有關閉,那麼closePath()
將會用直線連接當前端點和起始端點。
來看下面這段代碼,一條路徑是stroke()
在closePath()
前面(紅色折線);另一條路徑是stroke()
在closePath()
後面(藍色折線):
function drawScreen () {
ctx.strokeStyle = 'red';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(50, 10);
ctx.lineTo(150, 10);
ctx.lineTo(150,200);
ctx.lineTo(200,200);
ctx.lineTo(200,150);
ctx.stroke();
ctx.closePath();
ctx.strokeStyle = 'blue';
ctx.beginPath();
ctx.moveTo(250, 10);
ctx.lineTo(350,10);
ctx.lineTo(350,200);
ctx.lineTo(400,200);
ctx.lineTo(400,150);
ctx.closePath();
ctx.stroke();
}
很明顯,紅色的終點和起點沒連在一起,而藍色的則連起來了。
對於上面的這種多條線段(路徑),如果我們在代碼中添加一個fill()
,這個時候效果就不是線條效果了,而是線條起點和終點連起來的一個圖形:
function drawScreen () {
ctx.strokeStyle = 'red';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(50, 10);
ctx.lineTo(150, 10);
ctx.lineTo(150,200);
ctx.lineTo(200,200);
ctx.lineTo(200,150);
ctx.stroke();
ctx.fill();
ctx.closePath();
ctx.strokeStyle = 'blue';
ctx.beginPath();
ctx.moveTo(250, 10);
ctx.lineTo(350,10);
ctx.lineTo(350,200);
ctx.lineTo(400,200);
ctx.lineTo(400,150);
ctx.closePath();
ctx.stroke();
ctx.fill();
}
同時,在上例的基礎上,如果把strokeStyle
換成fillStyle
,同時刪除代碼中的stroke()
。效果又不一樣:
function drawScreen () {
ctx.fillStyle = '#ddaae2';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(50, 10);
ctx.lineTo(150, 10);
ctx.lineTo(150,200);
ctx.lineTo(200,200);
ctx.lineTo(200,150);
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.moveTo(250, 10);
ctx.lineTo(350,10);
ctx.lineTo(350,200);
ctx.lineTo(400,200);
ctx.lineTo(400,150);
ctx.closePath();
ctx.fill();
}
這個時候,不管是fill()
在closePath()
前後,最終看到的效果都是一樣的。也就是說fill()
會把路徑填充成一個圖形。
簡單小結一下
在HTML5 Canvas中繪製直線只需要使用[CanvasRenderingContext2D](//developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D)
對象的幾個屬性和方法即可輕鬆實現。
屬性或方法 | 基本描述 |
---|---|
strokeStyle |
用於設置畫筆繪製路徑的顏色、漸變和模式 |
lineWidth |
定義繪製線條的寬度 |
beginPath() |
開始一個新的繪製路徑 |
moveTo(x,y) |
移動畫筆到指定的座標點(x,y) ,該點就是新的子路徑的起始點 |
lineTo(x,y) |
使用直線邊接當前端點和指定的座標點(x,y) |
stroke() |
沿着繪製路徑的座標點順序繪製直線 |
closePath() |
如果當前的繪製路徑是打開的,則關閉掉該繪製路徑 |
在Canvas的圖形繪製過程中,幾乎都是先按照一定順序先定下幾個座標點,也就是所謂的繪製路徑,然後再根據我們的需要將這些座標點用指定的方式連接起來,就形成了我們所需要的圖形。當我們瞭解了CanvasRenderingContext2D
對象的上述API後,那麼繪製線條就顯得非常簡單了。
線段與像素邊界
這是繪製線段的一個小細節。在說這個細節之前,咱們先來看一個小示例,就是繪製兩條簡單的直線。
function drawScreen () {
ctx.strokeStyle = 'red';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(350, 50);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(50.5, 100.5);
ctx.lineTo(350.5,100.5);
ctx.closePath();
ctx.stroke();
}
明顯第二條比第一條線。藉助製圖軟件放大功能,來看一下:
雖然我們在代碼中設置了lineWidth
的值爲1
,同樣的值,但繪製出來的結果卻不一樣,第一條的寬度變成了2
,而第二條的寬度是1
。這就是我們接下來要說的線段與像素邊界。
如果你在某2
個像素的邊界處繪製一條1
像素寬的線段,那麼該線段實際上會佔據2
個像素的寬度,如下圖所示:
如果在像素邊界處繪製一條1
像素寬的垂直線段,那麼Canvas的繪圖環境對象會試着將半個像素畫在邊界中線的右邊,將另外半個像素畫在邊界中線的左邊。
然而,在一個整像素的範圍內繪製半個像素寬的線段是不可能的,所以左右兩個方向上的半像素都被擴展爲1
個像素。正如上圖中左圖,本來我們想要將線段繪製在深灰色的區域內,但實際上瀏覽器卻將其延伸繪製到整個灰色的範圍內。
另一方面,我們來看看如果將線段繪製在某2
個像素之間的那個像素中,效果就如上圖中右圖。這條垂直線段繪製在兩個像素之間,這樣的話,中線左右兩端的那半個像素就不會再延伸了,它們合半起來恰好佔據1
像素的寬度。所以說,如果繪製一條真正1
像素寬的線段,你必須將該線段繪製在某兩個像素之間的那個像素中,而不能將它繪製在兩個像素的交界處。
再回過頭來看上一節中,繪製的網格線,我們可以看到它的線條寬度其實不是真正的1px
。也就是上面說的原因造成的。既然明白原因了,那我們就可以輕鬆修改上節中繪製的網格。
var x = 0.5;
var y = 0.5;
var w = myCanvas.width + .5;
var h = myCanvas.height + .5;
效果如下:
將上面示例,繪製網格的代碼,可以用JavaScript將其封裝成爲一個drawGrid()
函數。
function drawGrid(color, stepX, stepY) {
ctx.strokeStyle = color;
ctx.lineWidth = 0.5;
for (var i = stepX + 0.5; i < myCanvas.width; i += stepX) {
ctx.beginPath();
ctx.moveTo(i, 0);
ctx.lineTo(i, myCanvas.height);
ctx.stroke();
}
for (var i = stepY + 0.5; i < myCanvas.height; i += stepY) {
ctx.beginPath();
ctx.moveTo(0, i);
ctx.lineTo(myCanvas.width, i);
ctx.stroke();
}
}
其中color
是網格線顏色,stepX
是x
軸的網格間距,stepY
是y
軸的網格間距。只需要在drawScree()
中繪製網格線刻度,調用drawGrid()
函數:
function drawScreen() {
var dx = 50,
dy = 50,
// 初始座標原點
x = 0,
y = 0,
w = myCanvas.width,
h = myCanvas.height,
xy = 10;
while (y < h) {
y = y + dy;
//橫座標的數字
ctx.font = "1px Calibri";
ctx.fillText(xy, x, y);
xy += 10;
}
// 畫豎線
y =0;
xy =10;
while (x < w) {
x = x + dx;
//縱座標的數字
ctx.font = "1px Calibri";
ctx.fillText(xy,x,10);
xy+=10;
}
drawGrid('#000', 50,50);
}
最後的效果如下:
總結
這篇文章主要記錄瞭如何在Canvas中繪製線段(路徑)。簡單的說,使用moveTo(x,y)
、lineTo(x,y)
和stroke()
就可以繪製出一條線段,或者說多線段。另外使用lineWidth
可以改變線段的寬度,而strokeStyle
可以改變線段的顏色。不過,在Canvas中繪製路徑時記得在開始時先調用beginPath()
。
是不是很簡單呀。不知道大家發現沒有,在這篇文章中,看到繪製的線都是實線,那麼在Canavs中有沒有直接的API可以繪製虛線或者點劃線呢?先思考一下,我們後續來回答。
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
原文: http://www.w3cplus.com/canvas/draw-lines.html © w3cplus.com著作權歸作者所有。
個人建了前端學習羣,旨在一起學習前端。純淨、純粹技術討論,非前端人員勿擾!入羣加我微信:iamaixiaoxiao。