1. 程式人生 > >強大的畫圖標籤——Canvas(中級篇)

強大的畫圖標籤——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' );

得到結果為:
這裡寫圖片描述