Canvas:繪制線段

在Canvas中,線段也是路徑中的一種,被稱之為線性路徑。在Canvas中繪制線性路徑主要用到moveTo(x,y)、lineTo(x,y)和stroke()幾個方法。

先畫一條簡單的直線


Canvas畫一下直線非常的容易。眾所周之,兩點就能構成一條直線。使用兩個API就可以:moveTo()告訴你把畫筆移到Canvas畫布中的某個位置(直線的起點),然后通過lineTo()把畫筆移到另一個點。從而兩個點構成一條直線。

functiondrawScreen(){

ctx.moveTo(50,10);? ?

?ctx.lineTo(350,100);

}

但這樣在畫布看不到任何的線條。如果需要看到效果,還需要使用stroke()方法:

functiondrawScreen(){

ctx.moveTo(50,10);? ??

ctx.lineTo(350,100);? ??

ctx.stroke();

}

是不是很簡單,通過這三個方法就可以繪制出一條線段。

moveTo(x,y):移動畫筆到指定的坐標點(x,y),該點就是新的子路徑的起始點。該方法并不會從當前路徑中清除任何子路徑

lineTo(x,y):使用直線連接當前端點和指定的坐標點(x,y)。

stroke():沿著繪制路徑的坐標點順序繪制直線

改變線段寬度

我們在實際繪制線段時,總是有粗細的情況發生。那么在Canvas中可以通過lineWidth來改變繪制線段的粗細。比如:

functiondrawScreen(){

ctx.lineWidth =10;// 改變線的粗細

ctx.moveTo(50,10);// 起始點

ctx.lineTo(350,100);// 第二點(如果是一條直線的話,就是終點)

ctx.stroke();}

lineWidth主要用來定義繪制線條的寬度。默認值是1.0,并且這個屬性必須大于0.0。較寬的線條在路徑上居中,每邊各有線條寬的一半。

改變線段的顏色

既然能改為線段的粗細,那必然能改變線段的顏色。在Canvas中可以通過strokeStyle來改變線段的顏色:

functiondrawScreen(){

ctx.lineWidth =10;? ??

ctx.strokeStyle ='#f36';? ??

ctx.moveTo(50,10);? ??

ctx.lineTo(350,100);? ??

ctx.stroke();

}

strokeStyle主要用于設置畫筆繪制路徑的顏色、漸變和模式。該屬性的值可以是一個表示CSS顏色值的字符串。如果你的繪制需求比較復雜,該屬性的值還可以是一個CanvasGradient對象或者CanvasPattern對象。

也就是說,我們也可以繪制漸變色的線段:

functiondrawScreen(){? ?// 創建一個表示線性顏色漸變的CanvasGradient對象,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// 并設置該對象的作用區域就是線段所在的區域varcanvasGradient = 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()不同點,我們可以繪制不同的路徑。

functiondrawScreen(){

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

}

把上面的代碼稍做修改:

functiondrawScreen(){

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()后面(藍色折線):

functiondrawScreen(){

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(),這個時候效果就不是線條效果了,而是線條起點和終點連起來的一個圖形:

functiondrawScreen(){

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()。效果又不一樣:

functiondrawScreen(){

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后,那么繪制線條就顯得非常簡單了。

線段與像素邊界

這是繪制線段的一個小細節。在說這個細節之前,咱們先來看一個小示例,就是繪制兩條簡單的直線。

functiondrawScreen(){

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像素寬的線段,你必須將該線段繪制在某兩個像素之間的那個像素中,而不能將它繪制在兩個像素的交界處。

所有瀏覽器的Canvas實現都使用了抗鋸齒技術,以便創建出亞像素線段的繪制效果來。

再回過頭來看上一節中,繪制的網格線,我們可以看到它的線條寬度其實不是真正的1px。也就是上面說的原因造成的。既然明白原因了,那我們就可以輕松修改上節中繪制的網格。

varx =0.5;

vary =0.5;

varw = myCanvas.width +.5;

varh = myCanvas.height +.5;

效果如下:

將上面示例,繪制網格的代碼,可以用JavaScript將其封裝成為一個drawGrid()函數。

functiondrawGrid(color, stepX, stepY){

ctx.strokeStyle = color;? ??

ctx.lineWidth =0.5;

for(vari = stepX +0.5; i < myCanvas.width; i += stepX) {

ctx.beginPath();? ? ? ??

ctx.moveTo(i,0);? ? ? ??

ctx.lineTo(i, myCanvas.height);? ? ? ??

ctx.stroke();? ??

}

for(vari = 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()。

?https://www.w3cplus.com/canvas/draw-lines.html???w3cplus.com

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,310評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,951評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,566評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,055評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,303評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,799評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,683評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,899評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,135評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,528評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,844評論 2 372

推薦閱讀更多精彩內容