與圓和圓弧相關的基礎知識
在學習如何繪製圓和圓弧以前,有一些相關的基礎知識有必要先進行了解。css
- 角度旋轉
- 角度和弧度
- 正切
角度旋轉
在座標系中,旋轉分爲順時針和逆時針兩個方向旋轉:html
角度和弧度
在CSS中,作旋轉經常使用到的都是角度(deg
)。但在Canvas中繪製圓或圓弧時用到的是弧度(rad
)。維基百科中是這樣描述弧度的:前端
弧度又稱弳度,是平面角的單位,也是國際單位制導出單位。單位弧度定義爲圓弧長度等於半徑時的圓心角。角度以弧度給出時,一般不寫弧度單位,或有時記爲
rad
。java
一個完整的圓的弧度是2π
,因此2π rad = 360°
,1 π rad = 180°
,1°=π/180 rad
,1 rad = 180°/π
(約57.29577951°
)。以度數表示的角度,把數字乘以π/180
便轉換成弧度;以弧度表示的角度,乘以180/π
便轉換成度數。node
rad = (π / 180) * deg
一樣的:canvas
deg = (rad * 180) / π
平時咱們常看到的各類弧度以下:ruby
JavaScript中弧度角度換算
僅難了解角度和弧度之間的關係是不夠的,咱們還須要知道怎麼使用JavaScript來實現角度和弧度之間的換算。一個π
大約是3.141592653589793
,在JavaScript中對應的是Math.PI
。那麼角度和弧度之間的換算:app
rad = (Math.PI * deg) / 180
一樣的:dom
deg = (rad * 180) / Math.PI
爲了方便計算和使用,能夠將其封裝成JavaScript函數:
function getRads (degrees) { return (Math.PI * degrees) / 180; } function getDegrees (rads) { return (rads * 180) / Math.PI; }
好比咱們要將30deg
轉換成rad
,能夠直接使用:
getRads(30); // 0.5235987755982988rad getDegrees(0.7853981633974483); // 45deg
下圖展現了常見的角度和弧度之間的換算:
正切
正切(Tangent,tan,也做tg)是三角函數的一種。它是周期函數,其最小正週期爲π
(Math.PI
)。正切函數是奇函數。
在Canvas中經常須要和三角函數打交道,這也說明了數學是多麼的重要,真後悔當初沒有認真學。有關於Canvas中三角函數的運用,後面咱們將會花很大的篇幅來介紹。
爲何在畫圓要提到正切呢?那是由於咱們後面在介紹artTo()
時會涉及到正切相關的知識。下圖能夠說明,正切和圓以及圓弧之間的關係,看上去一點複雜,但不用急於求成,後面會慢慢懂的:
有了這些基礎,咱們就能夠開始學習在Canvas中怎麼畫圓和圓弧了。這也是這篇文章真正的主題,若是你等不及了,那繼續日後閱讀。
arc()方法
先來看arc()
方法怎麼繪製圓和圓弧。Canvas中的arc()
方法接受六個參數:
arc(x, y, radius, startRad, endRad, [anticlockwise]);
在Canvas畫布上繪製以座標點(x,y)
爲圓心、半麼爲radius
的圓上的一段弧線。這段弧線的起始弧度是startRad
,結束弧度是endRad
。這裏的弧度是以x
軸正方向爲基準、進行順時針旋轉的角度來計算。其中anticlockwise
表示arc()
繪製圓或圓弧是以順時針仍是逆時針方向開始繪製。若是其值爲true
表示逆時針,若是是false
表示爲順時針。該參數是一個可選參數,若是沒有顯式設置,其值是false
(也是anticlockwise
的默認值)。
記得當初咱們學數時,圓的周長與半徑的關係是:C = πd
或C = 2πr
。具有這些基礎,咱們就可使用arc()
繪製弧線或圓了。
繪製弧線
先來看arc()
繪製弧線,根據上面介紹的內容,傳對應參數給他:
function drawScreen () { // x,y => 圓心座標點 // r => 圓弧半徑 var arc = { x: myCanvas.width / 2, y: myCanvas.height / 2, r: 100 }, w = myCanvas.width, h = myCanvas.height; ctx.save(); ctx.lineWidth = 10; ctx.strokeStyle = '#e3f'; // startRad => getRads(-45) // endRad => getRads(45) // 順時針旋轉 ctx.beginPath(); ctx.arc(arc.x, arc.y, arc.r,getRads(-45),getRads(45)); ctx.stroke(); // startRad => getRads(-135) // endRad => getRads(135) // 逆時針旋轉 ctx.beginPath(); ctx.strokeStyle = "#f36"; ctx.arc(arc.x, arc.y, arc.r,getRads(-135),getRads(135),true); ctx.stroke(); ctx.restore(); }
當咱們把上面的stroke()
換fill()
,上面的效果就不是一個弧線了:
另外在stroke()
以前調用closePath()
,那麼弧線的起始點和終止點將會以一條直接鏈接在一塊兒。好比上面的示例,加上以後的效果:
ctx.beginPath(); ctx.arc(arc.x, arc.y, arc.r,getRads(-45),getRads(45)); ctx.closePath(); ctx.stroke();
arc()
繪製弧線是否是很簡單,在實際中,藉助一些條件循環,咱們能夠作一些有意思的效果。好比下面的這個示例,使用arc()
繪製一個聲波波率放大圖:
function drawScreen () { var arc = { x: myCanvas.width / 2, y: myCanvas.height / 2, r: 10 }, w = myCanvas.width, h = myCanvas.height; ctx.save(); ctx.lineWidth = 1; ctx.strokeStyle = '#e3f'; for(var i = 0;i < 10; i++){ ctx.beginPath(); ctx.arc(arc.x, arc.y, arc.r * i,getRads(-45),getRads(45)); ctx.stroke(); ctx.beginPath(); ctx.arc(arc.x, arc.y, arc.r * i,getRads(-135),getRads(135),true); ctx.stroke(); } }
特別注意:
- 使用
arc()
繪製圖形時,若是沒有設置moveTo()
那麼會從圓弧的開始的點(startRad
處)做爲起始點。若是設置了moveTo()
,那麼該點會連線到圓弧起始點。 - 若是使用
stroke()
方法,那麼會從開始連線到圓弧的起始位置。 若是是 fill 方法, 會自動閉合路徑填充
繪製制圓
使用arc
繪製圓和繪製圓弧是同樣的,只不過繪製圓的時候startRad
和endRad
是相同的。好比:
function drawScreen () { var arc = { x: myCanvas.width / 2, y: myCanvas.height / 2, r: 50 }, w = myCanvas.width, h = myCanvas.height; ctx.save(); ctx.lineWidth = 2; ctx.strokeStyle = '#fff'; ctx.fillStyle = '#000'; // 繪製一個邊框圓 ctx.beginPath(); ctx.arc(arc.x / 2, arc.y, arc.r, getRads(0), getRads(360), false); ctx.stroke(); // 繪製一個閉合邊框圓 ctx.beginPath(); ctx.arc(arc.x, arc.y, arc.r, getRads(0), getRads(360), false); ctx.closePath(); ctx.stroke(); // 繪製一個填充圓 ctx.beginPath(); ctx.arc(arc.x * 1.5, arc.y, arc.r,getRads(0), getRads(360), true); ctx.fill(); //繪製一個帶邊框填充的圓 ctx.beginPath(); ctx.arc(arc.x, arc.y, arc.r / 2,getRads(0), getRads(360), false); ctx.stroke(); ctx.fill(); ctx.restore(); }
來作個小練習,使用arc()
繪製一個太極圖:
- 繪製一個白色和黑色大半圓,拼成一個圓形
- 繪製制一個小的白色半圓和另外一個黑色小半圓
- 繪製一個白色和黑色小圓點
這幾個組合起來,就是咱們想要的太極圖:
function drawScreen () { var arc = { x: myCanvas.width / 2, y: myCanvas.height / 2, r: 100 }, w = myCanvas.width, h = myCanvas.height; ctx.save(); ctx.lineWidth = 1; // 繪製白色大圓 ctx.beginPath(); ctx.fillStyle = '#fff'; ctx.arc(arc.x, arc.y, arc.r, getRads(-90), getRads(90), false); ctx.fill(); // 繪製黑色大圓 ctx.beginPath(); ctx.fillStyle = '#000'; ctx.arc(arc.x, arc.y, arc.r, getRads(-90), getRads(90), true); ctx.fill(); // 繪製白色小圓 ctx.beginPath(); ctx.fillStyle = '#fff'; ctx.arc(arc.x, arc.y - arc.r/2, arc.r / 2,getRads(-90), getRads(90), true); ctx.fill(); // 繪製黑色小圓 ctx.beginPath(); ctx.fillStyle = '#000'; ctx.arc(arc.x, arc.y + arc.r/2, arc.r / 2,getRads(-90), getRads(90), false); ctx.fill(); // 繪製小黑點 ctx.beginPath(); ctx.fillStyle = '#000'; ctx.arc(arc.x, arc.y - arc.r/2, arc.r / 10,getRads(0), getRads(360), false); ctx.fill(); // 繪製小白點 ctx.beginPath(); ctx.fillStyle = '#fff'; ctx.arc(arc.x, arc.y + arc.r/2, arc.r / 10,getRads(0), getRads(360), false); ctx.fill(); }
繪製扇形
使用arc()
除了能夠繪製弧線和圓以外,還能夠繪製扇形。繪製扇形關鍵點是經過moveTo()
把起始點位置設置爲圓心處,而後經過closePath()
閉合路徑。
function drawScreen () { var arc = { x: myCanvas.width / 2, y: myCanvas.height / 2, r: 100 }, w = myCanvas.width, h = myCanvas.height; ctx.save(); ctx.lineWidth = 1; ctx.strokeStyle = '#e3f'; ctx.fillStyle = '#e3f'; ctx.beginPath(); // 起始點設置在圓心處 ctx.moveTo(arc.x, arc.y); ctx.arc(arc.x, arc.y, arc.r,getRads(-45),getRads(45)); // 閉合路徑 ctx.closePath(); ctx.stroke(); ctx.beginPath(); // 起始點設置在圓心處 ctx.moveTo(arc.x, arc.y); ctx.arc(arc.x, arc.y, arc.r,getRads(-135),getRads(135),true); // 閉合路徑 ctx.closePath(); ctx.fill(); ctx.restore(); }
利用這個原理,能夠很輕鬆的實現一個餅圖效果。
特別聲明:
arc()
方法中的起始弧度參數startRad
和結束弧度參數endRad
都是以弧度爲單位,即便你填入一個數字,例如360
,仍然會被看做是360
弧度。
arcTo()方法
前面學習了arc()
方法如何繪製弧線、圓或扇形等。在Canvas中CanvasRenderingContext2D
還提供了另外一個方法arcTo()
用來繪製弧線,但arcTo()
繪製不出圓。爲何呢?接下來,我們就來了解arcTo()
的使用方法。
arcTo()
接受五個參數:
arcTo(x1, y1, x2, y2, radius)
arcTo()
方法將利用當前端點、端點一(x1, y1)
和端點二(x2, y2)
這三點所造成的夾角,而後繪製一段與夾角的兩邊相切而且半徑爲radius
的圓上的弧線。弧線的起點就是當前端點所在邊與圓的切點,弧線的終點就是商端點二(x2,y2)
所在邊與圓的切點,而且繪製的弧線是兩個切點之間長度最短的那個圓弧。此外,若是當前端點不是弧線起點,arcTo()
方法還將添加一條當前端點到弧線起點的直線線段。
上面理解起來有點吃力。事實上,arcTo()
儘管是經過兩點和半徑繪製弧線或圓,但事實上是有三個點參與。也就是說,不論是否調用arcTo()
,其實就有一個點已經存在(x0,y0)
。這樣就(x0,y0)
和(x1,y1)
構成一線,而後(x1,y1)
和(x2,y2)
構成一線,這兩條線交叉點就是(x1,y1)
。而後以radius
繪製的圓弧或圓都會與這兩條線相切。這也就是在文章開頭提正切的基礎。
或者換下圖,你能更好的理解。
在 arcTo()
函數中,雖然參數只涉及到 P1
也就是參數中的(x1,y1)
和 P2
也就是參數中的(x2,y2)
兩個點,實際上還有一個隱含的點,就是畫布上的當前點(P0
)也就是前面所說的(x0,y0)
。當 P0
, P1
, P2
不重疊也不在一條直線的時候,這 3
個點能夠構成一個三角形。想象一下,從 P0
開始,向 P1
畫一條線段,從 P1
開始到 P2
再畫一條線段,這兩條線段造成一個夾角,而後以 r
畫一個圓,移動這個圓將這個圓與線段 P0P1
和線段 P1P2
相切(也可能切點是在 P0P1
或者 P1P2
的延長線上),而後保留朝向 P1
這個點的弧線,就是 arcTo()
在弧線這部分作的事情。
實際繪製是這樣的:
moveTo()
給出P0
點座標arcTo()
函數中的參數給出了P1
點和P2
點的座標,以及圓形的半徑r
- 計算以
r
爲半徑的圓和直線P0P1
以及P1P2
的切點,記爲S
點和E
點,對應圖上Start
和End
兩個點 - 從
P0
向S
點畫出一條線段 - 從
S
點到E
點,畫出一段圓弧,半徑爲r
- 此時,畫布當前點爲
E
點 - 而後從
E
點又畫了一條線段到P2
點,至此arcTo
的工做已經完成,接下來你能夠stroke()
或者fill()
了
來看一個繪製過程:
function drawScreen () { ctx.lineWidth = 1; ctx.strokeStyle = '#f36'; ctx.fillStyle = 'red'; // 一個起始點 ( 100, 50 ), 那麼繪製其點. 顏色設置爲紅色 ctx.fillRect( 100 - 4, 50 - 4, 8, 8 ); // 兩個參考點分別爲 ( 100, 200 ) 和 ( 300, 200 ), 繪製出該點 ctx.fillRect( 100 - 4, 200 - 4, 8, 8 ); ctx.fillRect( 300 - 4, 200 - 4, 8, 8 ); // 鏈接兩個參考點 ctx.beginPath(); ctx.strokeStyle = 'red'; ctx.moveTo( 100, 200 ); ctx.lineTo( 300, 200 ); ctx.stroke(); // 調用 arcTo 方法繪製圓弧. 記得將起始點設置爲 ( 100, 50 ) ctx.beginPath(); ctx.strokeStyle = 'blue'; ctx.moveTo( 100, 50 ); ctx.arcTo( 100, 200, 300, 200, 80); ctx.stroke(); }
繪製帶圓角矩形
在學習Canvas中繪製矩形一節時,咱們提到經過lineJoin
改變線段端點形狀來模擬一個圓角矩形。經過這樣的方法繪製帶圓角的矩形,侷限性仍是很是大的。不過值得慶達的是,acrTo()
能夠輕易實現兩線內切圓弧,言外之意,使用arcTo()
來繪製一個圓角矩形是很是的方便。
圓角矩形是由四段線條和四個1/4
圓弧組成,拆解以下。
如此一來,咱們就能夠封裝一個函數,用來繪製圓角矩形。根據上圖,咱們能夠給這個函數,好比drawRoundedRect()
函數傳遞對應的參數:
ctx
:Canvas畫布繪圖環境x,y
:左上角width
:矩形寬度height
:矩形高度r
:矩形圓角半徑fill
: 繪製一個填充的矩形stroke
:繪製一個邊框矩形
開始封裝函數:
function drawRoundedRect(ctx, x, y, width, height, r, fill, stroke) {
ctx.save();
ctx.beginPath();
// draw top and top right corner ctx.moveTo(x + r, y); ctx.arcTo(x + width, y, x + width, y + r, r); // draw right side and bottom right corner ctx.arcTo(x + width, y + height, x + width - r, y + height, r); // draw bottom and bottom left corner ctx.arcTo(x, y + height, x, y + height - r, r); // draw left and top left corner ctx.arcTo(x, y, x + r, y, r); if (fill) { ctx.fill(); } if (stroke) { ctx.stroke(); } ctx.restore(); }
函數封裝好以後,只須要調用,就能夠輕易繪製出帶圓角的矩形,並且簡單易用:
function drawScreen () { ctx.strokeStyle = 'rgb(150,0,0)'; ctx.fillStyle = 'rgb(0,150,0)'; ctx.lineWidth = 7; drawRoundedRect(ctx, 30, 50, 200, 220, 20, true, true); ctx.strokeStyle = 'rgb(150,0,150)'; ctx.fillStyle = 'rgba(0,0,150,0.6)'; ctx.lineWidth = 7; drawRoundedRect(ctx, 300, 100, 250, 150, 8, true, false); }
是否是很簡單。能夠來個複雜點的練習。好比,當初火熱的2048遊戲。
使用drawRoundedRect()
函數就能夠很輕易的繪製出來,有興趣的同窗不仿一試。
繪製月亮
這節主要學習了arc()
和arcTo()
兩個方法,學習了怎麼使用這兩個方法在Canvas中如何繪製圓弧或圓。那麼咱們來看一個小示例,結合二者來繪製一月亮。
用一張圖來闡述繪製月亮的原理:
此圖已經說明一切,直接上代碼吧:
function drawMoon(cxt, d, x, y, R, rot){ cxt.save(); cxt.translate(x, y); cxt.scale(R, R); cxt.rotate(Math.PI / 180 * rot); pathMoon(cxt, d); cxt.fillStyle = 'hsl' + randomColor(); cxt.fill(); cxt.restore(); } //畫路徑 function pathMoon(cxt, d){ //D表示控制點的橫座標; cxt.beginPath(); cxt.arc(0, 0, 1, Math.PI * 0.5, Math.PI * 1.5, true); cxt.moveTo(0, -1); cxt.arcTo(d, 0, 0, 1, dis(0, -1, d, 0) * 1 / d); cxt.closePath(); } function dis(x1, y1, x2, y2){ return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); } function drawScreen () { drawMoon(ctx,2,myCanvas.width / 2,myCanvas.height / 2,100,15); }
總結
在Canvas中,CanvasRenderingContext2D
對象提供了兩個方法(arc()
和arcTo()
)來繪製圓和圓弧。其中arc()
便可繪製弧線,圓,也能夠繪製扇形,但arcTo()
僅能繪製出弧線。但arcTo()
能夠更輕易的幫助咱們實現帶圓角的矩形。到目前爲止,咱們學習了在Canvas中繪製線段、矩形、弧線和圓等,但基本圖形不只僅這些,接下來咱們將學習如何在Canvas中繪製箭頭。感興趣的同窗,歡迎持續關注相關更新。