canvas 可以用于動(dòng)畫(huà)、游戲畫(huà)面、數(shù)據(jù)可視化、圖片編輯以及實(shí)時(shí)視頻處理等內(nèi)容。
canvas api 主要聚焦于 2D 圖形。
WebGL api 主要聚焦于硬件加速的 2D 和 3D 圖形。
先來(lái)看一個(gè)基礎(chǔ)事例:
<canvas id="canvas">
Your browser not support canvas, please update browser.
</canvas>
<script>
function draw() {
// 獲取 canvas 元素
const canvas = document.getElementById('canvas');
// 創(chuàng)建畫(huà)板
const context = canvas.getContext('2d');
// 填充形狀
context.fillRect(10, 10, 55, 50);
// 填充顏色
context.fillStyle = 'black';
}
draw();
</script>
這時(shí),瀏覽器會(huì)出現(xiàn)一個(gè)黑色的方塊:

繪制圖形
首先學(xué)習(xí)如何繪制矩形。
繪制矩形
canvas 提供了 3 種方法來(lái)繪制矩形:
- fillRect(x, y, width, height): 繪制了一個(gè)填充的矩形;
- strokeRect(x, y, width, height): 繪制了一個(gè)矩形的邊框;
- clearRect(x, y, width, height): 清楚指定矩形區(qū)域,讓清楚部分完全透明;
- rect(x, y, width, height): 矩形路徑
使用以上三個(gè)方法構(gòu)建一個(gè)空心矩形:
function draw() {
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
context.fillRect(25, 25, 100, 100);
context.clearRect(50, 50, 50, 50);
}
draw();
如圖下所示:

繪制路徑
繪圖的基本元素是路徑。
路徑:通過(guò)不同顏色和寬度的線段或曲線相連形成的不同形狀的點(diǎn)的集合
一下是所需要的函數(shù):
- beginPath(): 新建一條路徑,生成之后,圖形繪制命令被指向到路徑上生成路徑;
- closePath(): 閉合路徑之后圖形繪制命令又重新指向到上下文中;
- stroke(): 通過(guò)線條來(lái)繪制圖形輪廓;
- fill(): 通過(guò)填充路徑的內(nèi)容區(qū)域生成實(shí)心的圖形了;
用以上的方法來(lái)構(gòu)建一個(gè)矩形:
function draw() {
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
context.beginPath();
context.moveTo(100, 100);
context.lineTo(100, 0);
context.lineTo(0, 0);
context.lineTo(0, 100);
context.fill();
}
draw();
生成路徑:
beginPath(): 本質(zhì)上,路徑是由很多子路徑構(gòu)成,這些子路徑都是在一個(gè)列表中,所有的子路徑來(lái)構(gòu)成圖形。每次調(diào)用這個(gè)方法,列表都會(huì)清空,然后就可以重新繪制新的圖形了;
moveTo(): 設(shè)置路徑之后,指定起始位置;
lineTo(): 調(diào)用函數(shù)繪制路徑;
closePath(): 閉合路徑(非必需),在調(diào)用 fill() 函數(shù)時(shí),所有沒(méi)有閉合的形狀都會(huì)自動(dòng)閉合。
移動(dòng)筆
moveTo(): 從畫(huà)布上的一個(gè)點(diǎn)移動(dòng)到另外一個(gè)點(diǎn)。
繪制直線
使用 lineTo(x, y) 方法:從當(dāng)前位置到 (x, y) 的指導(dǎo)位置的線段。
繪制圓弧
- arc(x, y, radius, startAngle, endAngle, anticlockwise): 畫(huà)一個(gè)以 (x, y) 為圓心的以 radius 為半徑的圓弧/園。從 startAngle 開(kāi)始,到 endAngle 結(jié)束,按照 anticlockwise 給定的方向。
- arcTo(x1, y1, x2, y2, radius): 根據(jù)給定的控制點(diǎn)和半徑畫(huà)一段圓弧,再以直線鏈接兩個(gè)控制點(diǎn)。
arc()函數(shù)中,表示角度的單位是弧度,不是角度。角度轉(zhuǎn)弧度的公式為:弧度 = 角度 * (π/ 180)
使用以上方法,畫(huà)一個(gè)實(shí)心圓:
function draw() {
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
context.beginPath();
context.moveTo(50, 50);
context.arc(50, 50, 25, 0, 2 * Math.PI, true);
context.fill();
}
draw();
如圖下所示:

Path2D
為了簡(jiǎn)化代碼以及提高性能,我們可以使用 Path2D 用來(lái)緩存或者記錄繪畫(huà)命令。
來(lái)實(shí)驗(yàn)一下,來(lái)畫(huà)一個(gè)內(nèi)切圓:
function draw() {
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
const rectangle = new Path2D();
rectangle.rect(0, 0, 100, 100);
const circle = new Path2D();
circle.moveTo(50, 50);
circle.arc(50, 50, 50, 2 * Math.PI, false);
context.stroke(rectangle);
context.stroke(circle);
}
draw();

SVG Path
可以使用 svg path data 來(lái)初始化 canvas 上面的路徑
到現(xiàn)在為止,我們可以使用一系列路徑來(lái)將我們所需要的圖形,繪畫(huà)在畫(huà)布上面了。大體上的步驟如下:
- 創(chuàng)建畫(huà)布
- 開(kāi)始路徑
- 設(shè)置初始點(diǎn)
- 繪畫(huà)路徑
- 閉合路徑
示例
接下來(lái),就通過(guò)上面學(xué)習(xí)到的知識(shí)來(lái)畫(huà)一個(gè)簡(jiǎn)單的柱狀圖:
軸線
生成軸線:
// 坐標(biāo)軸生成
createAxle(type = 'x') {
const {point, axleLength} = this.defaults;
const axle = new Path2D();
axle.moveTo(...point);
if (type === 'x') {
axle.lineTo(point[0] + axleLength, point[1]);
} else {
axle.lineTo(point[0], point[1] - axleLength);
}
this.context.stroke(axle);
}
刻度
生成刻度:
// 生成刻度
createScale(type = 'x') {
const {point, count, axleLength, scaleLength} = this.defaults;
const ruleWidth = parseInt(axleLength / count, 10);
const scale = new Path2D();
for (let i = 0; i <= axleLength; i += ruleWidth) {
if (type === 'x') {
scale.moveTo(point[0] + i, point[1]);
scale.lineTo(point[0] + i, point[1] + scaleLength);
} else {
scale.moveTo(point[0], point[1] - i);
scale.lineTo(point[0] - scaleLength, point[1] - i);
}
}
this.context.stroke(scale);
}
畫(huà)出柱狀圖
最后一步,我們就要畫(huà)出柱狀圖的關(guān)鍵,柱子了。
// 畫(huà)柱狀圖
createRect() {
const {point, axleLength, count} = this.defaults;
const rectLength = parseInt(axleLength / count, 10);
const rect = new Path2D();
for (let i = 0; i <= count; i += 1) {
rect.rect(
point[0] + (rectLength * i),
point[1],
rectLength,
-rectLength * i
);
}
this.context.fill(rect);
}
完整代碼
class LineChart {
constructor(config) {
const canvas = document.getElementById('canvas');
this.context = canvas.getContext('2d');
this.defaults = {
point: [50, 120],
axleLength: 100,
count: 5,
scaleLength: 5
};
Object.assign(this.defaults, config);
}
// 坐標(biāo)軸生成
createAxle(type = 'x') {
const {point, axleLength} = this.defaults;
const axle = new Path2D();
axle.moveTo(...point);
if (type === 'x') {
axle.lineTo(point[0] + axleLength, point[1]);
} else {
axle.lineTo(point[0], point[1] - axleLength);
}
this.context.stroke(axle);
}
// 坐標(biāo)標(biāo)尺
createScale(type = 'x') {
const {point, count, axleLength, scaleLength} = this.defaults;
const ruleWidth = parseInt(axleLength / count, 10);
const scale = new Path2D();
for (let i = 0; i <= axleLength; i += ruleWidth) {
if (type === 'x') {
scale.moveTo(point[0] + i, point[1]);
scale.lineTo(point[0] + i, point[1] + scaleLength);
} else {
scale.moveTo(point[0], point[1] - i);
scale.lineTo(point[0] - scaleLength, point[1] - i);
}
}
this.context.stroke(scale);
}
// 畫(huà)柱狀圖
createRect() {
const {point, axleLength, count} = this.defaults;
const rectLength = parseInt(axleLength / count, 10);
const rect = new Path2D();
for (let i = 0; i <= count; i += 1) {
rect.rect(
point[0] + (rectLength * i),
point[1],
rectLength,
-rectLength * i
);
}
this.context.fill(rect);
}
draw() {
this.createAxle('x');
this.createAxle('y');
this.createScale('x');
this.createScale('y');
this.createRect();
}
}
const lineChart = new LineChart();
lineChart.draw();
結(jié)果如下所示:

剩下的工作還有一些,之后在更新吧!
- 美化這個(gè)柱狀圖啊,現(xiàn)在這個(gè)太黑了
- 標(biāo)上刻度
- 根據(jù)數(shù)據(jù)來(lái)對(duì)柱子進(jìn)行調(diào)節(jié)