canvas從基礎(chǔ)api到畫出基本柱狀圖

創(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);

詳細(xì)參數(shù)參考文檔

畫個最基本的柱狀圖

canvas.jpg

首選我們分析一下,要實(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();
}

參考文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市报辱,隨后出現(xiàn)的幾起案子与殃,更是在濱河造成了極大的恐慌,老刑警劉巖碍现,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件幅疼,死亡現(xiàn)場離奇詭異,居然都是意外死亡昼接,警方通過查閱死者的電腦和手機(jī)爽篷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來慢睡,“玉大人逐工,你說我怎么就攤上這事∑” “怎么了泪喊?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長髓涯。 經(jīng)常有香客問我袒啼,道長,這世上最難降的妖魔是什么复凳? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任瘤泪,我火速辦了婚禮,結(jié)果婚禮上育八,老公的妹妹穿的比我還像新娘对途。我一直安慰自己,他們只是感情好髓棋,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布实檀。 她就那樣靜靜地躺著,像睡著了一般按声。 火紅的嫁衣襯著肌膚如雪膳犹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天签则,我揣著相機(jī)與錄音须床,去河邊找鬼。 笑死渐裂,一個胖子當(dāng)著我的面吹牛豺旬,可吹牛的內(nèi)容都是我干的钠惩。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼族阅,長吁一口氣:“原來是場噩夢啊……” “哼篓跛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起坦刀,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤愧沟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后鲤遥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沐寺,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年渴频,在試婚紗的時候發(fā)現(xiàn)自己被綠了芽丹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡卜朗,死狀恐怖拔第,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情场钉,我是刑警寧澤蚊俺,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站逛万,受9級特大地震影響泳猬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜宇植,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一得封、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧指郁,春花似錦忙上、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至腰懂,卻和暖如春梗逮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背绣溜。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工慷彤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓瞬欧,卻偏偏與公主長得像贷屎,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子艘虎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容