創(chuàng)建畫布
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>canvas</title>
</head>
<body>
// 可以在創(chuàng)建設(shè)置寬高 也可以使用js動態(tài)設(shè)置畫布寬高
<canvas id="canvas" height="1000" width="1000"></canvas>
</body>
<script>
<script>
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var cx = canvas.width = 1000;
var cy = canvas.height = 1000;
</script>
</html>
常用的 API
- fill() 填充路徑
- stroke() 描邊
- arc() 創(chuàng)建圓弧
- rect() 創(chuàng)建矩形
- fillRect() 繪制矩形路徑區(qū)域
- strokeRect() 繪制矩形路徑描邊
- clearRect() 在給定的矩形內(nèi)清楚指定的像素
- arcTo() 創(chuàng)建兩切線之間的弧/曲線
- beginPath() 起使一條路徑轰异,或者重置當(dāng)前路徑
- moveTo() 把路徑移動到畫布的指定點(diǎn)裹驰,不創(chuàng)建線段
- lineTo() 添加一個新的點(diǎn) 然后在畫布中創(chuàng)建改點(diǎn)到最后指定點(diǎn)的線條
- closePath() 創(chuàng)建當(dāng)前點(diǎn)回到起始點(diǎn)的路徑
- clip() 從原始畫布剪切任意形狀的區(qū)域
- quadraticCurveTo() 創(chuàng)建二次方貝塞爾曲線
- bezierCurveTo() 創(chuàng)建三次方貝塞爾曲線
- isPointInPath() 如果指定的點(diǎn)位于當(dāng)前路徑中斟叼,則返回 true 否則 false
- canvas api 文檔 不明白的可以參考文檔
接下來我們實(shí)現(xiàn)一個個小例子來熟悉這些 api
畫一個三角形
想要實(shí)現(xiàn)一個三角形,首先我們要學(xué)會怎么畫線段
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.beginPath();
// ctx.moveTo(x,y) xy是畫布中坐標(biāo)
ctx.moveTo(100, 100);
// ctx.lineTo(x,y)
ctx.lineTo(100, 200);
// 設(shè)置描邊樣式
ctx.strokeStyle = "#000";
// 對路徑進(jìn)行描邊
ctx.stroke();
// 這樣我們就畫了一個線段
既然我們畫了一個線段混滔,那么我們用三條線段不就組成了一個三角形嗎
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(100, 100);
ctx.lineTo(100, 200);
ctx.lineTo(200, 150);
ctx.lineTo(100, 100);
ctx.stroke();
// 這樣我們就畫了一個空心的三角形 如果想畫一個實(shí)心的三角形,我們可以把ctx.stroke()修改為 ctx.fill()
畫個圓
畫個圓還是比較簡單的钮呀,因?yàn)樘峁┙o我固定的 api 使用
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.arc(100, 100, 50, 0, Math.PI * 2, false);
// 可以控制畫圓的顏色
ctx.fillStyle = "#000";
ctx.closePath();
ctx.fill(); // 實(shí)心圓 空心圓可以用 ctx.stroke()
// context.arc(x, y, radius, startAngle, endAngle [, anticlockwise]);
// x對應(yīng)圓心橫坐標(biāo) y對應(yīng)圓心縱坐標(biāo) radius 半徑 startAngle 開始角度 endAngle結(jié)束角度 anticlockwise順時針還是逆時針
畫個漸變
如何設(shè)置漸變
- createLinearGradient() 創(chuàng)建線性漸變
- createPattern() 在指定方向上重復(fù)指定的元素
- createRadialGradient() 創(chuàng)建放射性漸變
- addColorStop() 規(guī)定漸變的顏色 和位置
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
// ctx.createLinearGradient(x0,y0,x1,y1);
// x0:開始漸變的 x 坐標(biāo)
// y0:開始漸變的 y 坐標(biāo)
// x1:結(jié)束漸變的 x 坐標(biāo)
// y1:結(jié)束漸變的 y 坐標(biāo)
var grd = ctx.createLinearGradient(0, 0, 100, 0);
grd.addColorStop(0, "pink");
grd.addColorStop(1, "red");
ctx.fillStyle = grd;
ctx.fillRect(0, 0, 200, 200);
繪制文本
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.font = "48px serif";
// 實(shí)心文本 10 荣德,50 代表的是字的起始坐標(biāo)位置
ctx.fillText("Hello world", 10, 50);
//空心文本
ctx.strokeText("Hello world", 10, 50);
繪制圖片
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var img = new Image();
img.onload = function () {
// 0 0 圖片的起始坐標(biāo)
ctx.drawImage(img, 0, 0);
};
img.src = "https://mdn.mozillademos.org/files/5395/backdrop.png";
drawImage 還有一些其他可選的參數(shù)
drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
畫個最基本的柱狀圖
首選我們分析一下,要實(shí)現(xiàn)這樣一個柱狀圖要做哪些事情
- 實(shí)現(xiàn)坐標(biāo)軸
- 實(shí)現(xiàn)刻度
- 畫矩形
- 繪制文本
總體可以分為這幾種命满,我可以一個一個來實(shí)現(xiàn)涝滴,分解功能更容易理解
實(shí)現(xiàn)坐標(biāo)軸
坐標(biāo)軸的實(shí)現(xiàn)實(shí)際上就是兩個線段,參考之前的教程相信我們都已經(jīng)會實(shí)現(xiàn)線短胶台,那么 實(shí)現(xiàn)坐標(biāo)軸豈不是輕而易舉歼疮,開始之前我們要先定義一些變量方便我們之后的使用
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
// 定義一些畫布屬性
let cWidth = canvas.width, // 畫布寬度
cHeight = canvas.height, // 畫布高度
cPadding = 80, //畫布上下左右的編劇
yAxisH = cHeight - cPadding * 2, // y軸的高度 等于畫布高度減去上下的邊距
xAxisW = cWidth - cPadding * 2, // x軸的寬度 等于畫寬度減去左右邊距
originX = cPadding, // 原點(diǎn)橫坐標(biāo)
originY = yAxisH + cPadding, // 原點(diǎn)縱坐標(biāo)
yAxisNum = 10, // y軸分段
xAxisNum = 0, // x軸分段 根據(jù)數(shù)據(jù)分
data = [
[2001, 100],
[2002, 200],
[2003, 300],
[2004, 400],
[2005, 500],
[2006, 600],
]; // 數(shù)據(jù)
然后定義一個初始化函數(shù),我們就開始畫坐標(biāo)系诈唬,有沒有發(fā)現(xiàn)我們的刻度實(shí)際上也是線段韩脏,所以我們可以封裝一下畫線的函數(shù),上代碼
// 調(diào)用初始化函數(shù)
init();
// 初始化
function init() {
xAxisNum = data.length;
// 首先我們要畫一個坐標(biāo)系
// 畫坐標(biāo)系
drawAxis();
// 畫刻度線
drawMarker();
// 畫柱子
drawBar();
}
// 畫坐標(biāo)系
function drawAxis() {
// 畫坐標(biāo)系首先畫線段铸磅,因?yàn)橛泻芏嗟胤蕉夹枰嬀€赡矢,所以我們封裝一個劃線的函數(shù)
// 定義線條的顏色
ctx.strokeStyle = "#333";
// y軸
drawLine(cPadding, cPadding, cPadding, cHeight - cPadding);
// x軸
drawLine(cPadding, cHeight - cPadding, cWidth - cPadding, cHeight - cPadding);
}
// 劃線函數(shù)
function drawLine(x, y, x1, y1) {
// 劃線我們需要兩個點(diǎn)就可以畫出目標(biāo)線段,原點(diǎn)和目標(biāo)點(diǎn)
ctx.beginPath();
ctx.lineWidth = 1; // 線寬
ctx.moveTo(x, y);
ctx.lineTo(x1, y1);
ctx.stroke();
ctx.closePath();
}
有沒有發(fā)現(xiàn)阅仔,我們的實(shí)現(xiàn)的坐標(biāo)系比較模糊,
canvas 繪圖時济竹,會從兩個物理像素的中間位置開始繪制并向兩邊擴(kuò)散 0.5 個物理像素。當(dāng)設(shè)備像素比為 1 時霎槐,一個 1px 的線條實(shí)際上占據(jù)了兩個物理像素(每個像素實(shí)際上只占一半),由于不存在 0.5 個像素梦谜,所以這兩個像素本來不應(yīng)該被繪制的部分也被繪制了丘跌,于是 1 物理像素的線條變成了 2 物理像素袭景,視覺上就造成了模糊
我們可以通過偏移畫布來解決問題
function init() {
xAxisNum = data.length;
// 首先我們要畫一個坐標(biāo)系
ctx.translate(0.5, 0.5); // 解決畫圖很模糊的問題
// 畫坐標(biāo)系
drawAxis();
// 畫刻度線
drawMarker();
// 畫柱子
drawBar();
ctx.translate(-0.5, -0.5); // 解決畫圖很模糊的問題
}
這樣看起來是不是比之前清晰很多
實(shí)現(xiàn)刻度和文字
刻度也都是一個一個線段,只要我們給出每個刻度的坐標(biāo)即可
// 刻度函數(shù)
function drawMarker() {
//y軸 刻度
let yVal = yAxisH / yAxisNum; // 刻度間的距離
ctx.textAlign = "right";
for (let i = 0; i <= yAxisNum; i++) {
// 繪制文本
ctx.fillText(i * 100, originX - 10, originY - i * yVal + 7);
if (i > 0) {
this.ctx.strokeStyle = "#333";
// 刻度
drawLine(originX, originY - i * yVal, originX - 5, originY - i * yVal);
this.ctx.strokeStyle = "#4F94CD";
// x軸輔助線
drawLine(
originX,
originY - i * yVal,
cWidth - cPadding,
originY - i * yVal,
true
);
}
}
ctx.save();
ctx.font = "16px Arial";
ctx.rotate(-Math.PI / 2);
ctx.fillText("產(chǎn)量", -cHeight / 2, 40);
ctx.restore();
// x軸刻度
let xVal = xAxisW / xAxisNum;
ctx.strokeStyle = "#333";
ctx.textAlign = "center";
for (let i = 0; i < xAxisNum; i++) {
ctx.fillText(data[i][0], originX + (i + 1) * xVal - xVal / 2, originY + 16);
drawLine(
originX + (i + 1) * xVal,
originY,
originX + (i + 1) * xVal,
originY + 6
);
}
ctx.save();
ctx.font = "16px Arial";
ctx.fillText("年份", (cWidth - cPadding) / 2, cHeight - cPadding + 50);
ctx.restore();
}
因?yàn)槲覀兊妮o助線添加了虛線闭树,所以我畫線函數(shù)需要修改下
// 劃線函數(shù)
function drawLine(x, y, x1, y1, z = false) {
// 劃線我們需要兩個點(diǎn)就可以畫出目標(biāo)線段耸棒,原點(diǎn)和目標(biāo)點(diǎn)
ctx.beginPath();
// 加入是否需要虛線的判斷
if (z) {
// 虛線
ctx.setLineDash([4, 4]);
}
}
實(shí)現(xiàn)矩形
function drawBar() {
let xVal = xAxisW / xAxisNum; // x刻度間的距離大小
// 柱子寬度
let barW = xVal / 2;
for (let i = 0; i < xAxisNum; i++) {
// 數(shù)據(jù)應(yīng)該占據(jù)y軸的高度
let barH = (data[i][1] * yAxisH) / 1000;
let x = originX + i * xVal + barW / 2;
let y = originY - barH;
// 畫矩形
drawRect(x, y, barW, barH);
// 寫文字
ctx.fillStyle = "#333";
ctx.fillText(data[i][1], x + barW / 2, y - 10);
}
}
// 畫矩形
function drawRect(x, y, w, h) {
ctx.beginPath();
ctx.rect(x, y, w, h);
ctx.fillStyle = "#1C86EE";
ctx.fill();
ctx.closePath();
}