前端開發系列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
方法只是規劃了矩形的路徑,並沒有填充和描邊,因此還需要搭配stroke
或fill
使用。
描邊矩形
語法
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();
作用
開始路徑的作用是將不同的繪製路徑進行隔離,閉合路徑會自動連線最開始和最後的點。
注意
執行開始路徑方法時表示將要重新繪製一個新的路徑,可以分開設定和管理多個路徑的樣式。
二、 Canvas路徑繪製示例
路徑繪製DemoDemo-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');