強大的畫圖標籤——Canvas(中級篇)
2.中級篇
2.1. 繪製形狀
繪製圖形不僅僅是利用線條來實現繪圖, 還可以有快捷的繪製圖形的辦法
1.繪製矩形
2.繪製圓弧
2.1.1. 繪製矩形
繪製矩形的方法:
1.CanvasRenderingContext2D.strokeRect
2.CanvasRenderingContext2D.fillRect
3.CanvasRenderingContext2D.rect
注意: rect 方法就是矩形路徑, 還需要使用 fill 或 stroke 才可以看到效果. 因此一般使用 strokeRect 或 fillRect 直接可以看到結果.
清除矩形區域:CanvasRenderingContext2D.clearRect
2.1.1.1. 繪製矩形框
語法: CanvasRenderingContext2D.strokeRect( x, y, width. height )
描述:
1.用來繪製一個矩形. 比起直接使用 moveTo 和 lineTo 方法要簡單許多.
2.該方法的前兩個引數表示繪製矩形的左上角的座標. 後兩個引數表示這個矩形的寬高.
3.使用該方法不需要使用 moveTo 方法設定起始點, 也不需要呼叫 stroke 等繪畫方法.
4.繪製的矩形支援 strokeStyle 設定顏色樣式.
案例:
ctx.strokeStyle = ‘red’;
ctx.strokeRect( 100, 100, 200, 100 );
效果:
2.1.1.2. 繪製填充矩形
語法: CanvasRenderingContext2D.fillRect( x, y, width. height )
描述:
1.用來繪製一個矩形. 比起直接使用 moveTo 和 lineTo 方法要簡單許多.
2.該方法的前兩個引數表示繪製矩形的左上角的座標. 後兩個引數表示這個矩形的寬高.
3.使用該方法不需要使用 moveTo 方法設定起始點, 也不需要呼叫 stroke 等繪畫方法.
4.繪製的矩形支援 fillStyle 設定顏色樣式.
案例:
ctx.fillStyle = ‘green’;
ctx.fillRect( 100, 100, 200, 100 );
效果:
2.1.1.3. 清除矩形區域
語法: CanvasRenderingContext2D.clearRect( x, y, width, height )
描述:
1.用於清除畫布中的矩形區域的內容.
2.引數 x, y 表示矩形區域左上角的座標, width 與 height 表示矩形區域的寬高.
案例:
ctx.fillRect( 100, 100, 200, 100 );
ctx.clearRect( 110, 110, 50, 50 );
效果:
2.1.1.4. 案例
利用繪製圖形與清除矩形區域, 可以實現簡單的動畫. 例如程式碼:
var x = 10, y = 10, oldx = 10, oldy = 10;
var width = 100, height = 50;
var intervalId = setInterval(function () {
ctx.clearRect( oldx - 1, oldy - 1, width + 2, height + 2 );
ctx.strokeRect( x, y, width, height );
oldx = x;
oldy = y;
x += 4;
y += 2;
if ( oldy >= 200 ) {
// clearInterval( intervalId );
x = 10, y = 10;
}
}, 20);
效果:
有時為了簡單常常將整個畫布都清除, 這樣就不用每次計算清除的問題.
ctx.clearRect( 0, 0, cas.width, cas.height );
// 也可以設定畫布寬度, 這樣就會自動清除
cas.width = cas.width;
2.1.2. 繪製圓弧
繪製圓弧的方法有
CanvasRenderingContext2D.arc()
CanvasRenderingContext2D.arcTo()
2.1.2.1. 繪製圓弧
語法: CanvasRenderingContext2D.arc( x, y, radius. startAngle. endAngle, anticlockwise )
描述:
1.該方法用於繪製一段弧, 配合開始點的位置 與 stroke 方法或 fill 方法可以繪製扇形.
2.方法中的前兩個引數 x, y 表示繪製圓弧的圓心座標.
3.引數 radius 表示圓弧半徑, 單位為弧度.
4.引數 startAngle 與 endAngle 表示開始到結束的角度. 角度以水平向右為 0 弧度, 順時針為正方向.
5.引數 anticlockwise 表示是否採用預設的正向角度, 如果傳入 true 表示逆指標為正. 該引數可選.
案例:
// 在 200, 200 的地方繪製一段半徑為 100 的圓弧, 圓心角為 - PI / 2 到 PI / 4
...
ctx.arc( 200, 200, 100, -Math.PI/2, Math.PI/4 );
ctx.stroke();
// 為了方便看清楚結構, 繪製座標軸
ctx.beginPath();
ctx.strokeStyle = 'red';
ctx.moveTo( 50, 200 );
ctx.lineTo( 350, 200 );
ctx.moveTo( 200, 50 );
ctx.lineTo( 200, 350 );
ctx.moveTo( 200, 200 );
ctx.lineTo( 300, 300 );
ctx.stroke();
效果:
2.1.2.1.1. 注意事項
1.使用 arc 繪圖的時候, 如果沒有設定 moveTo 那麼會從開始的繪弧的地方作為起始點. 如果設定了 moveTo, 那麼會連線該點與圓弧的起點.
2.如果使用 stroke 方法, 那麼會從開始連線到圓弧的起始位置. 如果是 fill 方法, 會自動閉合路徑填充.
例如:
2.1.2.2. 繪製扇形
繪製扇形的重點是需要設定起始位置為圓心點, 然後閉合路徑即可
...
ctx.strokeStyle = 'red';
ctx.fillStyle = 'pink';
ctx.moveTo( 100, 200 );
ctx.arc( 100, 200, 100, -Math.PI/3, Math.PI/3 );
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo( 300, 200 );
ctx.arc( 300, 200, 100, -Math.PI/3, Math.PI/3 );
ctx.closePath();
ctx.fill();
效果:
2.1.2.3. 繪製扇形動畫
繪製扇形動畫, 就是每隔幾毫秒( 20 毫秒)擦除以前繪製的內容, 然後在以前繪製的基礎上比以前多繪製一點東西. 這裡多繪製的內容就是由角度決定. 比如一開始角度從 -Math.PI / 2 開始繪製. 那麼每次角度都 +0.1, 直到 繪製到 Math.PI * 3 / 2 為止.
...
ctx.fillStyle = 'green';
var startAngle = -Math.PI / 2,
angle = startAngle,
x = 200, y = 200,
r = 100;
var intervalId = setInterval(function () {
// 清除之前繪製的內容
ctx.clearRect( 0, 0, cas.width, cas.height );
// 角度增量
angle += 0.1;
// 判斷是否停止計時器
if ( angle >= Math.PI * 3 / 2 ) {
clearInterval( intervalId);
angle = Math.PI * 3 / 2;
console.log( '繪製完成' );
}
// 繪製
ctx.moveTo( x, y );
ctx.arc( x, y, r, startAngle, angle );
ctx.fill();
}, 20);
2.1.2.4. 繪製餅形圖
繪製餅形圖最大的特點是角度是疊加的. 開始從 -Math.PI/2 開始繪製, 達到執行角 x 後, 下一個區域從 x 開始繪製, 然後有到一個角 y 停下來. 如此反覆到 Math.PI * 3 / 2 結束.
2.1.2.4.1. 三等分餅形圖
繪製一個三等分的餅形圖, 顏色使用 紅, 綠, 藍.
var x = 200, y = 200,
r = 100,
step = Math.PI * 2 / 3, // 120 度一個區域
start = -Math.PI / 2, // 起始角度
colors = [ 'red', 'green', 'blue' ];
for ( var i = 0; i < 3; i++ ) {
ctx.beginPath();
ctx.moveTo( x, y );
ctx.fillStyle = colors[ i ];
ctx.arc( x, y, r, start, start+=step );
ctx.fill();
}
效果:
2.1.2.4.2. 根據資料定義角度
根據資料來源定義角度, 就是將所有的資料求和, 按照總和為 2 * Math.PI 的結論計算出每一個數據部分的弧度值. 同時顏色可以提前定義好.
從 Konva 庫中分離出來的顏色
var colors =
( "aliceblue,antiquewhite,aqua,aquamarine,azure,beige,bisque,black,blanchedalmond,blue," +
"blueviolet,brown,burlywood,cadetblue,chartreuse,chocolate,coral,cornflowerblue,cornsilk," +
"crimson,cyan,darkblue,darkcyan,darkgoldenrod,darkgray,darkgreen,darkgrey,darkkhaki,darkmagenta," +
"darkolivegreen,darkorange,darkorchid,darkred,darksalmon,darkseagreen,darkslateblue,darkslategray," +
"darkslategrey,darkturquoise,darkviolet,deeppink,deepskyblue,dimgray,dimgrey,dodgerblue,firebrick," +
"floralwhite,forestgreen,fuchsia,gainsboro,ghostwhite,gold,goldenrod,gray,green,greenyellow,grey," +
"honeydew,hotpink,indianred,indigo,ivory,khaki,lavender,lavenderblush,lawngreen,lemonchiffon," +
"lightblue,lightcoral,lightcyan,lightgoldenrodyellow,lightgray,lightgreen,lightgrey,lightpink," +
"lightsalmon,lightseagreen,lightskyblue,lightslategray,lightslategrey,lightsteelblue,lightyellow," +
"lime,limegreen,linen,magenta,maroon,mediumaquamarine,mediumblue,mediumorchid,mediumpurple," +
"mediumseagreen,mediumslateblue,mediumspringgreen,mediumturquoise,mediumvioletred,midnightblue," +
"mintcream,mistyrose,moccasin,navajowhite,navy,oldlace,olive,olivedrab,orange,orangered,orchid," +
"palegoldenrod,palegreen,paleturquoise,palevioletred,papayawhip,peachpuff,peru,pink,plum,powderblue," +
"purple,rebeccapurple,red,rosybrown,royalblue,saddlebrown,salmon,sandybrown,seagreen,seashell,sienna," +
"silver,skyblue,slateblue,slategray,slategrey,snow,springgreen,steelblue,tan,teal,thistle,transparent," +
"tomato,turquoise,violet,wheat,white,whitesmoke,yellow,yellowgreen" ).split( ',' );
如果得到資料
var data = [ 123, 156, 47, 100, 80 ];
那麼計算各個部分的比例時, 可以構造一個儲存分量值與弧度的物件陣列.
var sum = 0;
for ( var i = 0; i < data.length; i++ ) {
sum += data[ i ];
}
// 得到總數後, 分量比就有了
var odata = data.map(function ( v, i ) {
return { value: v, radius: v * 2 * Math.PI / sum };
});
最後根據資料開始繪圖
// 開始繪圖
var start = -Math.PI / 2,
x = 200, y = 200,
r = 100;
for ( var i = 0; i < odata.length; i++ ) {
ctx.beginPath();
ctx.fillStyle = colors[ i + 10 ];
ctx.moveTo( x, y );
ctx.arc( x, y, r, start, start+=odata[ i ][ 'radius' ] );
ctx.fill();
}
效果:
2.1.2.5. 繪製相切弧
語法: CanvasRenderingContext2D.arcTo( x1, y1, x2, y2, radius )
描述:
1.該方法用於繪製圓弧
2.繪製的規則是當前位置與第一個參考點連線, 繪製的弧與該直線相切.
3.同時連線兩個參考點, 圓弧根據半徑與該連線相切
例如有一個起始點 ( 100, 100 ), 那麼繪製其點. 顏色設定為紅色.
ctx.fillStyle = 'red';
ctx.fillRect( 100 - 4, 100 - 4, 8, 8 );
然後兩個參考點分別為 ( 100, 300 ) 和 ( 300, 300 ), 繪製出該點
ctx.fillRect( 100 - 4, 300 - 4, 8, 8 );
ctx.fillRect( 300 - 4, 300 - 4, 8, 8 );
連線兩個參考點
ctx.beginPath();
ctx.strokeStyle = 'red';
ctx.moveTo( 100, 300 );
ctx.lineTo( 300, 300 );
ctx.stroke();
得到效果為:
呼叫 arcTo 方法繪製圓弧. 記得將起始點設定為 ( 100, 100 )
ctx.beginPath();
ctx.strokeStyle = 'blue';
ctx.moveTo( 100, 100 );
ctx.arcTo( 100, 300, 300, 300, 100 );
ctx.stroke();
得到效果:
注意: 使用該方法可以使用圓弧連線兩條直線, 而不用計算複雜的起始角度與結束角度. 因此用於繪製圓角矩形等案例較多.
2.1.2.6. 繪製圓角矩形
封裝一個函式, 用於繪製圓角矩形.
1.參考 rect 方法, 需要座標引數 x, y.
2.由於設定圓角, 因此需要設定圓角半徑 cornerRadius.
3.還需要提供寬高.
首先繪製一個矩形邊框. 但是需要考慮圓角, 雖然從 x, y 開始繪製, 但是中間要空出 半徑的距離.
var x = 100, y = 100, width = 300, height = 100,
cornerRadius = 10;
ctx.strokeStyle = 'red';
ctx.moveTo( x + cornerRadius, y );
ctx.lineTo( x + width - cornerRadius, y );
ctx.moveTo( x + width, y + cornerRadius );
ctx.lineTo( x + width, y + height - cornerRadius );
ctx.moveTo( x + width - cornerRadius, y + height );
ctx.lineTo( x + cornerRadius, y + height );
ctx.moveTo( x, y + height - cornerRadius );
ctx.lineTo( x, y + cornerRadius );
ctx.stroke();
效果為:
然後再分別繪製四個角, 設定當前位置與參考點的位置. 設定當前位置為一個線端點, 然後參考點依次就是 矩形頂點 和 另一個線段的端點.
ctx.moveTo( x + cornerRadius, y );
ctx.arcTo( x, y, x, y + cornerRadius, cornerRadius );
即可得到:
同理繪製另外三個圓角
ctx.moveTo( x + width - cornerRadius, y );
ctx.arcTo( x + width, y, x + width, y + cornerRadius, cornerRadius );
ctx.moveTo( x + width, y + height - cornerRadius );
ctx.arcTo( x + width, y + height, x + width - cornerRadius, y + height, cornerRadius );
ctx.moveTo( x + cornerRadius, y + height );
ctx.arcTo( x, y + height, x, y + height - cornerRadius, cornerRadius );
即可得到:
封裝成方法就可以繪製更多圓角矩形了. 封裝中注意 beginPath() 和 save() 和 restore()
function cRect ( x, y, width, height, cornerRadius, color ) {
ctx.save();
ctx.beginPath();
ctx.strokeStyle = color || 'red';
ctx.moveTo( x + cornerRadius, y );
ctx.lineTo( x + width - cornerRadius, y );
ctx.moveTo( x + width, y + cornerRadius );
ctx.lineTo( x + width, y + height - cornerRadius );
ctx.moveTo( x + width - cornerRadius, y + height );
ctx.lineTo( x + cornerRadius, y + height );
ctx.moveTo( x, y + height - cornerRadius );
ctx.lineTo( x, y + cornerRadius );
// 開始繪製四個圓角
ctx.moveTo( x + cornerRadius, y );
ctx.arcTo( x, y, x, y + cornerRadius, cornerRadius );
ctx.moveTo( x + width - cornerRadius, y );
ctx.arcTo( x + width, y, x + width, y + cornerRadius, cornerRadius );
ctx.moveTo( x + width, y + height - cornerRadius );
ctx.arcTo( x + width, y + height, x + width - cornerRadius, y + height, cornerRadius );
ctx.moveTo( x + cornerRadius, y + height );
ctx.arcTo( x, y + height, x, y + height - cornerRadius, cornerRadius );
ctx.stroke();
ctx.restore();
}
呼叫程式碼:
cRect( 50, 50, 100, 50, 5 );
cRect( 100, 120, 100, 80, 8, 'blue' );
cRect( 300, 100, 200, 100, 10, 'green' );
得到結果為: