1. 程式人生 > 其它 >前端開發系列025-基礎篇之Canvas繪圖(路徑)

前端開發系列025-基礎篇之Canvas繪圖(路徑)

title: '前端開發系列025-基礎篇之Canvas繪圖(路徑)'
tags:
  - javaScript系列
categories: []
date: 2017-06-22 08:20:13
本文將介紹Canvas中的路徑、矩形以及描邊和填充等繪製方法,非零正交原則以及線性漸變等內容,並提供折線圖和柱狀圖等綜合案例。

一、Canvas路徑和狀態

核心API介紹

設定繪製的起點

語法 ctx.moveTo(x, y);

引數 第一個引數和第二個引數都是相對於Canvas畫布左上角的X軸和Y軸座標。

作用 設定Canvas上下文繪製路徑的起點,相當於設定畫筆從哪個位置開始移動。

注意 使用Canvas

上下文繪製路徑前必須先設定起點,否則繪製無效。

設定繪製目標點

語法 ctx.lineTo(x, y);

引數 第一個引數和第二個引數都是相對於Canvas畫布左上角的X軸和Y軸座標。

作用 設定Canvas上下文繪製路徑的目標點,相當於設定畫筆移動的目標位置。

設定描邊

語法 ctx.stroke();

作用 根據路徑來繪製(描邊),可以在繪製前通過strokeStyle來設定描邊樣式。

設定填充

語法 ctx.fill();

作用 對閉合路徑的內容進行繪製(填充),可以通過fillStyle來設定樣式,預設黑色。

引數 fill方法有兩個可選引數(nonzero | evenodd

) ,控制填充時使用環繞原則(預設)或奇偶原則。

矩形路徑

語法 ctx.rect(x, y, width, height);

引數 第一個引數和第二個引數都是矩形左上角座標的X和Y軸座標,第三和第四個引數為矩形的寬高。

注意 rect方法只是規劃了矩形的路徑,並沒有填充和描邊,因此還需要搭配strokefill使用。

描邊矩形

語法 ctx.strokeRect(x, y, width, height);

引數 第一個引數和第二個引數都是矩形左上角座標的X和Y軸座標,第三和第四個引數為矩形的寬高。

作用 該方法繪製完矩形路徑後立即進行stroke描邊繪製,等價於rect + stroke

組合。

填充矩形

語法 ctx.fillRect(x, y, width, height);

引數 第一個引數和第二個引數都是矩形左上角座標的X和Y軸座標,第三和第四個引數為矩形的寬高。

作用 該方法繪製完矩形路徑後立即進行fill填充繪製,等價於rect + fill方法的組合。

矩形擦除

語法 ctx.clearRect(x, y, width, hegiht);

引數 第一個引數和第二個引數都是矩形左上角座標的X和Y軸座標,第三和第四個引數為矩形的寬高。

作用 該方法用於擦除指定矩形內繪製的內容,需注意如果重置畫布寬度,內容將自動重繪。

開始和閉合路徑

語法 ctx.beginPath();ctx.closePath();

作用 開始路徑的作用是將不同的繪製路徑進行隔離,閉合路徑會自動連線最開始和最後的點。

注意 執行開始路徑方法時表示將要重新繪製一個新的路徑,可以分開設定和管理多個路徑的樣式。

路徑的核心屬性介紹 lineCap 設定或返回線條末端線帽樣式值有`butt預設`|`round圓形`|`square正方形` lineJoin設定或返回所建立邊角的型別,可選值有`miter預設`|`round圓角`|`bevel斜角` lineWidth設定或返回當前線條的寬度,以畫素計,預設值為1。 miterLimit設定或返回最大斜接長度。 fillStyle設定或返回用於填充繪畫的顏色、漸變或模式。 strokeStyle設定或返回用於筆觸(描邊)的顏色、漸變或模式。 shadowColor設定或返回用於陰影的顏色,和`shadowBlur`一起使用。 shadowBlur設定或返回用於陰影的模糊級別,和`shadowColor`一起使用。 shadowOffsetX設定或返回陰影距形狀的水平距離,0指示陰影位於形狀的正下方。 shadowOffsetY設定或返回陰影距形狀的垂直距離,0指示陰影位於形狀的正下方。

二、 Canvas路徑繪製示例

路徑繪製Demo

Demo-1 繪製交叉和平行線

    //01 繪製一條直線
    var canvas  = document.getElementById("canvas");
    var ctx     = canvas.getContext("2d");

    ctx.moveTo(20,20.5);
    ctx.lineTo(200,20.5);
    ctx.strokeStyle = "#195";   //設定描邊樣式
    ctx.stroke();

    //02 繪製兩條平行線
    ctx.moveTo(20.5,40);        //設定起點
    ctx.lineTo(20.5,120);       //設定目標點

    ctx.moveTo(40.5,40);        //設定起點
    ctx.lineTo(40.5,120);       //設定目標點
    ctx.stroke();               //繪製路徑(描邊)

    //03 繪製兩條交叉線條
    ctx.moveTo(60,60);          //設定起點
    ctx.lineTo(100,100);        //設定目標點
    ctx.moveTo(100,60);         //設定起點
    ctx.lineTo(60,100);         //設定目標點
    ctx.stroke();               //繪製路徑(描邊) 

Demo-2 開始路徑和閉合路徑

    var canvas = document.getElementById("canvas");
    var ctx    = canvas.getContext("2d");

    //001 繪製兩條交叉的線(演示beginPath方法的使用)
    //(1) 設定並繪製第一條線
    ctx.moveTo(50,50);
    ctx.lineTo(150,150);
    ctx.stroke();

    //(2) 設定並繪製第二條線
    ctx.beginPath();         //重新開啟路徑
    ctx.moveTo(50,150);
    ctx.lineTo(150,50);
    //設定線條和描邊的樣式
    ctx.strokeStyle = "red";
    ctx.stroke();

    //002 繪製兩條相接的線(演示closePath方法的使用)
    ctx.beginPath();        //重新開啟路徑
    ctx.moveTo(180.5,20);
    ctx.lineTo(180.5,180.5);
    ctx.lineTo(260,180.5);
    
    //設定關閉路徑(自動連線兩個點閉合以構成封閉區域)
    ctx.closePath();
    ctx.strokeStyle = "blue";
    ctx.stroke();
    
    //設定圖形填充和樣式
    ctx.fillStyle = "#eee";
    ctx.fill();

Demo-3 繪製虛線的N種方式

    var canvas = document.getElementById("canvas");
    var ctx    = canvas.getContext("2d");
    
    //001 繪製虛線的第一種方式(通過fillRect矩形繪製API)
    for(var i = 0 ; i < 100 ; i++)
    {
        //第一個引數:矩形的起點X
        //第二個引數:矩形的起點Y
        //第三個引數:矩形的寬度
        //第四個引數:矩形的高度
        ctx.fillRect((i *2),30,1,20);
        ctx.fillRect((i *2),70,1,1);
        ctx.fillRect((i *5),100,1,1);
    }

    //002 繪製虛線的第二種方式(通過路徑和setLineDash繪製API)
    ctx.moveTo(0,130.5);
    ctx.lineTo(200,130.5);
    ctx.setLineDash([5]);
    ctx.stroke();

    //開啟路徑繪製另一條虛線
    ctx.beginPath();
    ctx.moveTo(0,160.5);
    ctx.lineTo(200,160.5);
    //引數說明[第一段的長度、第二段的長度、第三段的長度 * 重複]
    ctx.setLineDash([5,10,15]);
    ctx.strokeStyle = "red";
    ctx.stroke();
    //獲取虛線的排列方式(不重複那段的排列方式)
    console.log(ctx.getLineDash()); //[5, 10, 15, 5, 10, 15]

    //開啟路徑繪製另一條虛線(偏移量參照)
    ctx.beginPath();
    ctx.moveTo(0,180.5);
    ctx.lineTo(200,180.5);
    //設定虛線的偏移量
    ctx.lineDashOffset = -30;
    ctx.setLineDash([5,10,15]);
    ctx.strokeStyle = "red";
    ctx.stroke();

Demo-4 繪製實心三角形和矩形(四邊形)

    var canvas = document.getElementById("canvas");
    var ctx    = canvas.getContext("2d");
   
    //001 使用路徑的方式繪製三角(邊)形
    ctx.moveTo(20,20);
    ctx.lineTo(100,20);
    ctx.lineTo(80,100);
    ctx.closePath();
    // ctx.lineTo(20,20);

    //繪製(填充)
    ctx.fillStyle = "#195";
    ctx.fill();
    
    //繪製(描邊)
    // ctx.stroke();

    //002 使用路徑的方式繪製四角(邊)形
    //備註:如果是填充的話,那麼只需要四個點的座標即可確定
    ctx.beginPath();
    ctx.moveTo(150,20);
    ctx.lineTo(350,20);
    ctx.lineTo(350,100);
    ctx.lineTo(150,100);
    ctx.lineTo(150,20);
    // ctx.closePath();
   
    //繪製(描邊)
    // ctx.strokeStyle = "red";
    // ctx.stroke();
    
    //繪製(填充)
    ctx.fillStyle = "blue";
    ctx.fill();

Demo-5 繪製矩形API使用示例

    var canvas = document.getElementById("canvas");
    var ctx    = canvas.getContext("2d");

    //繪製矩形API介紹
    //001 使用rect + stroke|fill 方法繪製矩形(非獨立路徑)
    //第一個引數:矩形左上角X
    //第二個引數:矩形左上角Y
    //第三個引數:矩形的寬度W
    //第四個引數:矩形的高度H
    ctx.rect(20,20,300,100);
    ctx.stroke();                   //繪製(描邊)
    // ctx.fill();                  //繪製(填充)

    //002 使用fillRect繪製(獨立路徑)
    ctx.fillStyle = "green";        //設定填充顏色
    ctx.fillRect(20,150,200,40);

    //003 使用strokeRect繪製(獨立路徑)
    ctx.strokeStyle = "red";        //設定描邊顏色
    ctx.strokeRect(20,210,200,50);

    //004 擦除畫布
    //ctx.clearRect(20,150,50,40);
柱狀圖綜合案例
    //繪製柱狀圖的建構函式
    var RectChart = function(ctx){
        this.rects = null;
        this.ctx    = ctx || document.getElementById("canvas").getContext("2d");
        
        //設定計算引數
        this.m      = 10;
        this.w      = this.ctx.canvas.width;
        this.h      = this.ctx.canvas.height;
        this.cols   = Math.floor(this.w / this.m);
        this.rows   = Math.floor(this.h / this.m);
        this.pointW = 6;
        this.x      = 50;
        this.y      = 350;
        this.rectW  = 40;
    }

    //設定原型物件
    RectChart.prototype = {
        constructor:RectChart,
        init:function(rects){
            this.rects = rects;
            this.drawGrid();
            this.drawAxis();
            this.drawRect();
        },
        drawGrid:function(){
            //002 設定路徑
            //[1] 繪製所有的行
            for(var i = 1 ; i < this.rows ; i++)
            {
                this.ctx.moveTo(0,(i * this.m)+0.5);
                this.ctx.lineTo(this.w,(i * this.m)+0.5);
            }

            //[2] 繪製所有的列
            for(var j = 1 ; j < this.cols ; j++)
            {
                this.ctx.moveTo((j * this.m) + 0.5,0);
                this.ctx.lineTo((j * this.m) + 0.5,this.h);
            }

            //003 繪製網格
            this.ctx.strokeStyle = "#ddd";
            this.ctx.stroke();
        },
        drawAxis:function(){
            //004 繪製座標(橫座標和縱座標 X-Y)
            var x = this.x,
                y = this.y,
                xl = 650,
                yl = 300,
                m  = this.m;
            this.ctx.beginPath();

            //繪製X軸座標
            this.ctx.moveTo(x,y);
            this.ctx.lineTo(x + xl,y);
            this.ctx.lineTo(x + xl - m,y - m/2);
            this.ctx.lineTo(x + xl - m,y - m/2 + m);
            this.ctx.lineTo(x + xl,y);
            this.ctx.fill();

            //繪製Y軸座標
            this.ctx.moveTo(x,y);
            this.ctx.lineTo(x,y - yl);
            this.ctx.lineTo(x - m/2,y - yl + m);
            this.ctx.lineTo(x - m/2 + m,y - yl + m);
            this.ctx.lineTo(x,y - yl);
            this.ctx.fill();

            this.ctx.strokeStyle = "#000";
            this.ctx.stroke();
        },
        drawRect:function(){
            var self = this;
            //繪製座標點的每條連線線
            this.ctx.beginPath();
            this.rects.forEach(function(rect){
                self.ctx.fillStyle = rect.color;
                self.ctx.fillRect(rect.x,self.y - rect.h,self.rectW,rect.h);
            })
            this.ctx.strokeStyle = "#000";
            this.ctx.stroke();
        }
    }

    //準備繪製資料
    var rects = [
        {x:100,h:50,color:"red"},
        {x:200,h:250,color:"pink"},
        {x:300,h:120,color:"#195"},
        {x:400,h:300,color:"#47e"},
        {x:500,h:20,color:"#302"}
      ]

    //呼叫建構函式繪製
    new RectChart().init(rects);

三、 Non-Zero Winding Number Rule & Odd-even Rule

非零正交(環繞)原則

我們在使用繪圖上下文物件的fill方法進行填充繪製的時候,如果傳遞引數(nonzero)或預設不傳遞任何引數,那麼在填充的時候使用非零正交(環繞)原則

非零正交(環繞)原則 · 規則

>❐ 在路徑包圍的區域中,向外發射一條和所有圍繞它的邊相交的射線
>❐ 開啟一個計數器,計數器的初始值為0
>❐ 如果這個射線遇到順時針圍繞,那麼計數器 +1
>❐ 如果這個射線遇到順時針圍繞,那麼計數器 -1
>❐ 如果最終計數器的值非〇,則這塊區域在路徑內瀏覽器會對其進行填充。

這裡我們可以給出兩個非零正交(環繞)原則應用的典型案例-繪製鏤空矩形和圓環。

    var canvas = document.getElementById("canvas");
    var ctx    = canvas.getContext("2d");
    
    //繪製正方形(順時針)
    ctx.moveTo(50,50);
    ctx.lineTo(150,50);
    ctx.lineTo(150,150);
    ctx.lineTo(50,150);
    ctx.lineTo(50,50);

    //繪製正方形(逆時針)
    ctx.moveTo(75,75);
    ctx.lineTo(75,125);
    ctx.lineTo(125,125);
    ctx.lineTo(125,75);
    ctx.lineTo(75,75);
    //設定填充(非零正交原則)
    ctx.fillStyle = "#299";
    ctx.fill();

    //繪製圓環
    ctx.beginPath();
    ctx.arc(300,100,60,0,2 * Math.PI,false);
    ctx.arc(300,100,40,0,2 * Math.PI,true);
    ctx.fillStyle = "rgba(250,50,79,1)";
    ctx.fill();
奇偶填充原則

我們在使用繪圖上下文物件的fill方法進行填充繪製的時候,如果傳遞引數(evenodd)那麼在填充的時候使用奇偶填充原則

奇偶填充原則 · 規則

>❐ 在路徑包圍的區域中,向外發射一條和所有圍繞它的邊相交的射線
>❐ 檢視相交線的個數,如果為奇數,就填充,如果是偶數,就不填充。
    var canvas  = document.getElementById('canvas');
    var ctx     = canvas.getContext('2d');
    var x = ctx.canvas.width / 2,
        y = ctx.canvas.height/ 2,
        r = 50,
        start = - Math.PI / 2,
        end = Math.PI * 3 / 2;

    ctx.arc(x, y, r, start, end);
    ctx.fillStyle = "#000";
    ctx.fill();
    ctx.beginPath();
    ctx.moveTo(x, y - r);
    ctx.lineTo(x - r * Math.sin(Math.PI / 5), y + r * Math.cos(Math.PI / 5));
    ctx.lineTo(x + r * Math.cos(Math.PI / 10), y - r * Math.sin(Math.PI / 10));
    ctx.lineTo(x - r * Math.cos(Math.PI / 10), y - r * Math.sin(Math.PI / 10));
    ctx.lineTo(x + r * Math.sin(Math.PI / 5), y + r * Math.cos(Math.PI / 5));
    ctx.fillStyle = "#fff";
    ctx.fill();

    ctx.beginPath();
    ctx.arc(x + 150, y, r, start, end);
    ctx.fillStyle = "#000";
    ctx.fill();
    ctx.beginPath();
    ctx.moveTo(x + 150, y - r);
    ctx.lineTo(x + 150 - r * Math.sin(Math.PI / 5), y + r * Math.cos(Math.PI / 5));
    ctx.lineTo(x + 150 + r * Math.cos(Math.PI / 10), y - r * Math.sin(Math.PI / 10));
    ctx.lineTo(x + 150 - r * Math.cos(Math.PI / 10), y - r * Math.sin(Math.PI / 10));
    ctx.lineTo(x + 150 + r * Math.sin(Math.PI / 5), y + r * Math.cos(Math.PI / 5));
    ctx.fillStyle = "#fff";
    ctx.fill('evenodd');