Canvas 入門(mén)指南

mdn 文檔

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è)黑色的方塊:

CleanShot 2021-05-17 at 10.55.23@2x.png

繪制圖形

首先學(xué)習(xí)如何繪制矩形。

繪制矩形

canvas 提供了 3 種方法來(lái)繪制矩形:

  1. fillRect(x, y, width, height): 繪制了一個(gè)填充的矩形;
  2. strokeRect(x, y, width, height): 繪制了一個(gè)矩形的邊框;
  3. clearRect(x, y, width, height): 清楚指定矩形區(qū)域,讓清楚部分完全透明;
  4. 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();

如圖下所示:

CleanShot 2021-05-17 at 11.21.35@2x.png

繪制路徑

繪圖的基本元素是路徑。

路徑:通過(guò)不同顏色和寬度的線段或曲線相連形成的不同形狀的點(diǎn)的集合

一下是所需要的函數(shù):

  1. beginPath(): 新建一條路徑,生成之后,圖形繪制命令被指向到路徑上生成路徑;
  2. closePath(): 閉合路徑之后圖形繪制命令又重新指向到上下文中;
  3. stroke(): 通過(guò)線條來(lái)繪制圖形輪廓;
  4. 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();

生成路徑:

  1. beginPath(): 本質(zhì)上,路徑是由很多子路徑構(gòu)成,這些子路徑都是在一個(gè)列表中,所有的子路徑來(lái)構(gòu)成圖形。每次調(diào)用這個(gè)方法,列表都會(huì)清空,然后就可以重新繪制新的圖形了;

  2. moveTo(): 設(shè)置路徑之后,指定起始位置;

  3. lineTo(): 調(diào)用函數(shù)繪制路徑;

  4. 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)位置的線段。

繪制圓弧

  1. arc(x, y, radius, startAngle, endAngle, anticlockwise): 畫(huà)一個(gè)以 (x, y) 為圓心的以 radius 為半徑的圓弧/園。從 startAngle 開(kāi)始,到 endAngle 結(jié)束,按照 anticlockwise 給定的方向。
  2. 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();

如圖下所示:

CleanShot 2021-05-17 at 11.50.08@2x.png

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();
CleanShot 2021-05-17 at 12.09.55@2x.png

SVG Path

可以使用 svg path data 來(lái)初始化 canvas 上面的路徑

到現(xiàn)在為止,我們可以使用一系列路徑來(lái)將我們所需要的圖形,繪畫(huà)在畫(huà)布上面了。大體上的步驟如下:

  1. 創(chuàng)建畫(huà)布
  2. 開(kāi)始路徑
  3. 設(shè)置初始點(diǎn)
  4. 繪畫(huà)路徑
  5. 閉合路徑

示例

接下來(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é)果如下所示:

CleanShot 2021-05-17 at 14.42.14@2x.png

剩下的工作還有一些,之后在更新吧!

  • 美化這個(gè)柱狀圖啊,現(xiàn)在這個(gè)太黑了
  • 標(biāo)上刻度
  • 根據(jù)數(shù)據(jù)來(lái)對(duì)柱子進(jìn)行調(diào)節(jié)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容