canvas繪製

基本用法

canvas和普通的html標籤用法差很少,canvas擁有width(默認300)和height(默認150)兩個屬性,有時出現模糊扭曲的狀況極可能是忘記設置width和height了,並非元素的style中的width和height。javascript

<canvas id="tutorial" width="300" height="150"></canvas>
複製代碼

經過元素已經建立好一個固定大小的繪畫區域,經過元素的getContext('2d')得到一個CanvasRenderingContext2D的繪畫上下文。也能夠經過元素是否有getContext方法來判斷瀏覽器是否支持cnavas(都2021年了!)。畫布的方向符合瀏覽器的滾動條屬性,左上爲 (0,0),向下是無窮大的Y軸,向右是無窮大的X軸。設置元素對應的width和height後坐標中的一格表明畫布的一個像素。css

const canvas = document.getElementById('tutorial');
if (canvas.getContext) {
  const ctx = canvas.getContext('2d');
  // drawing code here
} else {
  // canvas-unsupported code here
}
複製代碼

image.png
canvas只支持兩個基本形狀: 矩形和路徑(由直線鏈接的點列表)。全部其餘形狀都必須經過組合一個或多個路徑來建立。html

矩形

  • fillRect(x, y, width, height); // 繪製實心矩形,默認黑色
  • strokeRect(x, y, width, height); // 繪製矩形輪廓,默認#7a7a7a
  • clearRect(x, y, width, height); // 清除指定的矩形區域,使其徹底透明

x 和 y 指定矩形左上角在畫布上的位置(相對於原點)。width和height提供了矩形的大小。java

<!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>
  <style> body { background: #f5f5f5; } canvas { border: 1px solid #1890ff; } </style>
</head>

<body>
  <canvas id="tutorial" width="520" height="200"></canvas>
</body>
<script> const draw = ctx => { ctx.fillRect(10, 10, 300 - 20, 150 - 20); ctx.clearRect(20, 20, 300 - 40, (150 - 40) / 2); ctx.strokeRect(30, 30, 30, 30); } const render = () => { const canvas = document.getElementById('tutorial'); if (canvas.getContext) { const ctx = canvas.getContext('2d'); draw(ctx); } else { // canvas-unsupported code here } } window.setTimeout(render, 0); </script>

</html>
複製代碼

image.png

路徑

  • beginPath(); 經過清空子路徑列表啓動新路徑。在內部,路徑被存儲爲子路徑(線、弧等)的列表,這些子路徑一塊兒造成一個形狀。
  • closePath(); // 使路徑的尾節點和頭節點經過線段鏈接。
  • moveTo(); // 將新子路徑的起始點移動到指定的(x,y)座標
  • lineTo(); // 用直線將當前子路徑中的最後一個點鏈接到指定的(x,y)座標
  • quadraticCurveTo(); // 向當前路徑添加二次貝塞爾曲線
  • bezierCurveTo(); // 向當前路徑添加三次貝塞爾曲線
  • arc(); // 向當前路徑添加圓弧
  • arcTo(); // 用給定的控制點和半徑向當前路徑添加一條弧線,該弧線經過一條直線鏈接到前一個點
  • ellipse(); // 向當前路徑添加橢圓弧
  • stroke(); // 描邊,和strokeRect相似
  • fill(); // 填充,和fillRect相似
  1. 調用fill方法時任何打開的形狀將自動關閉,下個圖形的起點是上個圖形的終點,因此沒必要調用closePath()鏈接首尾
const draw = (ctx) => {
    ctx.beginPath();
    ctx.moveTo(10, 20);
    ctx.lineTo(10, 80);
    ctx.lineTo(40, 50);
    ctx.fill();
    ctx.lineTo(150, 50);
    ctx.stroke();
    ctx.closePath();
  }
複製代碼

image.png

  1. stroke方法並不會將形狀關閉,與fill同樣下個圖形的起點是上個圖形的終點
const draw = (ctx) => {
    ctx.beginPath();
    ctx.moveTo(10, 20);
    ctx.lineTo(10, 80);
    ctx.lineTo(40, 50);
    ctx.stroke();
    ctx.lineTo(150, 50);
    ctx.stroke();
    ctx.closePath();
  }
複製代碼

image.png

  1. 若是形狀已經關閉或只有一個點,調用closePath函數不執行任何操做;closePath調用後下個圖形的起點爲上個圖形moveTo的那個點
const draw = (ctx) => {
    ctx.beginPath();
    ctx.moveTo(10, 20);
    ctx.lineTo(10, 80);
    ctx.lineTo(40, 50);
    ctx.stroke();
    ctx.closePath();
    ctx.lineTo(150, 50);
    ctx.stroke();
    ctx.closePath();
  }
複製代碼

image.png

  1. 調用beginPath時會狀況列表建立一條新的路徑
const draw = (ctx) => {
    ctx.beginPath();
    ctx.moveTo(10, 20);
    ctx.lineTo(10, 80);
    ctx.lineTo(40, 50);
    ctx.stroke();
    ctx.beginPath();
    ctx.moveTo(10, 20);
    ctx.lineTo(150, 50);
    ctx.stroke();
    ctx.closePath();
  }
複製代碼

image.png

經過quadraticCurveTo、bezierCurveTo、arc、arcTo、ellipse能夠繪製一些奇奇怪怪的圖形chrome

樣式屬性

樣式屬性在設置以後會應用到後續的全部圖形繪製上,對於不一樣樣式的每一個形狀,都須要從新分配canvas

  • fillStyle // 填充顏色,默認#000000,rgb(0, 0, 0),rgba(0, 0, 0, 1)
  • strokeStyle // 描邊顏色,默認#000000,rgb(0, 0, 0),rgba(0, 0, 0, 1)
  • globalAlpha // 透明度0到1,默認爲1
  • lineWidth // 畫筆寬度,默認爲1
  • lineCap // 線的兩端的樣式,butt:兩端無樣式(默認),round:兩端加上圓形,square:兩端加上二分之一畫筆寬度的方形
  • lineJoin // 線條相交的「角」的外觀,round:填充圓形,bevel:填充三角形,miter:填充菱形(默認)
  • setLineDash() // 設置波浪線,參數爲數組 [ 實線長度 , 虛線長度 ]
  • lineDashOffset // 波浪線的偏移,默認爲0
const draw = ctx => {
    ctx.beginPath()
    ctx.lineWidth = 10
    ctx.strokeStyle = '#1890ff'
    ctx.moveTo(0, 20);
    ctx.lineTo(200, 20);
    ctx.setLineDash([20, 10])
    ctx.stroke();

    ctx.beginPath()
    ctx.moveTo(0, 40);
    ctx.lineWidth = 10
    ctx.setLineDash([])
    ctx.lineCap = 'round' // 兩端爲圓角
    ctx.lineJoin = 'round' // 線段交叉處爲圓角
    ctx.globalAlpha = 0.5
    ctx.lineTo(200, 40);
    ctx.lineTo(100, 80);
    ctx.stroke();
    ctx.closePath()
  }
複製代碼

image.png

漸變色api

  • 線性漸變: createLinearGradient(x1, y1, x2, y2),參數爲起點(x1,y1)和終點(x2,y2)
  • 徑向漸變: createRadialGradient(x1, y1, r1, x2, y2, r2),參數爲兩個圓
  • 角度漸變: createConicGradient(angle, x, y),參數爲弧度和位置,部分瀏覽器須要更改瀏覽器設置後可用,大多數瀏覽器不可用,注意兼容性問題

上述函數調用後會生成CanvasGradient對象,經過對象的addColorStop(offset, color)方法生成過渡色,offset取值爲0~1,表示漸變的起始位置數組

  1. 線性漸變
const draw = ctx => {
    const gradient = ctx.createLinearGradient(0, 0, 200, 0);
    gradient.addColorStop(0, 'green');
    gradient.addColorStop(.5, 'white');
    gradient.addColorStop(1, 'pink');
    ctx.fillStyle = gradient;
    ctx.fillRect(10, 10, 200, 100);
  }
複製代碼

image.png

  1. 徑向漸變
const draw = ctx => {
    const gradient = ctx.createRadialGradient(100, 100, 10, 100, 100, 70);
    gradient.addColorStop(0, 'green');
    gradient.addColorStop(.5, 'white');
    gradient.addColorStop(1, 'pink');
    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, 200, 200);
  }
複製代碼

image.png

  1. 角度漸變(谷歌瀏覽器須要在chrome://flags 中設置new-canvas-2d-api爲enabled)
const draw = ctx => {
    const gradient = ctx.createConicGradient(0, 100, 100);
    gradient.addColorStop(0, 'green');
    gradient.addColorStop(.5, 'white');
    gradient.addColorStop(1, 'pink');
    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, 200, 200);
  }
複製代碼

image.png

文本

  • fillText(text, x, y [, maxWidth]) // 在指定的(x,y)位置填充指定的text,繪製的最大寬度是可選的
  • strokeText(text, x, y [, maxWidth]) // 在指定的(x,y)位置繪製text邊框,繪製的最大寬度是可選的
  • font // 字體屬性,與css font屬性同樣
  • textAlign // 文本對齊選項;值爲start(默認), end, left, right, center
  • textBaseline // 基線對齊選項;值爲top, hanging, middle, alphabetic(默認), ideographic, bottom
  • direction // 文本方向;值爲ltr, rtl, inherit(默認)
const draw = ctx => {
    ctx.strokeStyle = "#1890ff";
    ctx.beginPath();
    ctx.moveTo(0,48);
    ctx.lineTo(500,48);
    ctx.stroke();
    ctx.closePath();
    ctx.font = "48px serif";
    ctx.textBaseline = "middle";
    ctx.strokeText("Hello world", 0, 48);
  }
複製代碼

image.png

陰影

  • shadowOffsetX // 陰影在 X 軸的延伸距離,負值表示陰影會往左延伸,正值則表示會往右延伸,默認爲 0
  • shadowOffsetY // 陰影在 Y 軸的延伸距離,負值表示陰影會往上延伸,正值則表示會往下延伸,默認爲 0
  • shadowBlur // 陰影模糊程度,默認爲 0
  • shadowColor // 陰影顏色,默認爲 rgba(0,0,0,0)
const draw = ctx => {
    ctx.fillStyle = "#1890ff";
    ctx.shadowOffsetX = 8;
    ctx.shadowOffsetY = 8;
    ctx.shadowBlur = 2;
    ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
    ctx.font = "32px Times New Roman";
    ctx.fillText("Hello World", 0, 32);
    ctx.fillRect(0, 64, 50, 50)
  }
複製代碼

image.png

圖片

  • drawImage(image, x, y)
  • drawImage(image, x, y, width, height)
  • drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)

若調用 drawImage 時,圖片沒裝載完,那什麼都不會發生(在一些舊的瀏覽器中可能會拋出異常)。所以應該用load事件來保證不會在加載完畢以前使用這個圖片。瀏覽器

  1. 3個參數的圖片繪製中, x,y 爲距離畫布原點的位置,圖片大小爲載入的圖片大小
const draw = ctx => {
    const img = new Image();
    img.onload = function () {
      ctx.drawImage(img, 0, 0);
    }
    img.src = 'https://sf3-ttcdn-tos.pstatp.com/img/mosaic-legacy/3795/3033762272~300x300.image';
  }
複製代碼

image.png

  1. 5個參數的圖片繪製中, 其餘參數和基本繪製同樣,但圖片的大小能夠自定義及 width,height 能夠對載入圖片進行拉伸或縮小
const draw = ctx => {
    const img = new Image();
    img.onload = function () {
      ctx.drawImage(img, 0, 0);
      ctx.drawImage(img, 200, 0, 90, 180);
    }
    img.src = 'https://sf3-ttcdn-tos.pstatp.com/img/mosaic-legacy/3795/3033762272~300x300.image';
  }
複製代碼

image.png

  1. 8個參數的圖片繪製中, sx,sy 爲在原圖上截取圖片的位置, sWidth,sHeight 爲截取的寬高, dx,dy 爲在畫布上繪製的位置, dWidth,dHeight 爲將截取下來的圖片繪製到畫布上的大小

image.png

const draw = ctx => {
    const img = new Image();
    img.onload = function () {
      ctx.drawImage(img, 0, 0);
      ctx.drawImage(img, 0, 0, 90, 90, 200, 0, 180, 180);
    }
    img.src = 'https://sf3-ttcdn-tos.pstatp.com/img/mosaic-legacy/3795/3033762272~300x300.image';
  }
複製代碼

image.png

進階用法

在繪製複雜圖形時必不可少的兩個方法:markdown

  • save() // 保存 canvas 的全部狀態
  • restore() // 恢復 canvas 狀態的

canvas 的狀態就是當前畫面應用的全部樣式和變形的一個快照。canvas狀態存儲在棧中,每當 save 方法被調用後,當前的狀態就被推送到棧中保存;每一次調用  restore 方法,上一個保存的狀態就從棧中彈出,全部設定都恢復。一個繪畫狀態包括:

  1. 當前應用的變形(即移動,旋轉和縮放等)
  2. 這些樣式屬性strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, lineDashOffset, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation, font, textAlign, textBaseline, direction, imageSmoothingEnabled
  3. 當前的裁切路徑

僅單獨調用方法時不能體現這兩個方法的用處,僅僅一個壓棧和出棧的過程:

// 僅調用save
const draw = ctx => {
    ctx.fillStyle = "#1890ff";
    ctx.fillRect(0, 0, 150, 150);
    ctx.save();
  }
// 僅調用restore
const draw = ctx => {
    ctx.fillStyle = "#1890ff";
    ctx.fillRect(0, 0, 150, 150);
    ctx.restore();
  }
// 僅調用save和restore
const draw = ctx => {
    ctx.fillStyle = "#1890ff";
    ctx.fillRect(0, 0, 150, 150);
    ctx.save();
    ctx.restore();
  }

/* 三種方式都是相同的結果:填充一個藍色的矩形 */
複製代碼

image.png

當調用save方法後當前畫筆的狀態不會被清除,仍然和save前的同樣,後續改了狀態後,調用restore後會將棧中的狀態取出並自動改變當前畫筆的狀態

const draw = ctx => {
    ctx.fillStyle = "#1890ff"; // 設置畫筆顏色爲藍色
    ctx.fillRect(0, 0, 30, 30); // 第一個矩形繪製
    ctx.save(); // 把藍色(#1890ff)壓入棧中

    ctx.fillRect(40, 0, 30, 30); // 第二個矩形繪製

    ctx.fillStyle = "#389E0D"; // 設置畫筆顏色爲綠色
    ctx.fillRect(80, 0, 30, 30); // 第三個矩形繪製
    ctx.save(); // 把綠色(#389E0D)壓入棧中

    ctx.fillStyle = "#F5222D"; // 設置畫筆顏色爲紅色

    ctx.restore(); // 彈出第二次壓棧的狀態
    ctx.fillRect(120, 0, 30, 30); // 第四個矩形繪製
    ctx.restore(); // 彈出第一次壓棧的狀態
    ctx.fillRect(160, 0, 30, 30); // 第五個矩形繪製
  }
複製代碼

image.png

縮放

scale(x, y) 方法能夠縮放畫布的水平和垂直的單位。兩個參數都是實數,能夠爲負數,x 爲水平縮放因子,y 爲垂直縮放因子,若是比1小,會縮小圖形,若是比1大會放大圖形。默認值爲1,爲實際大小。

const draw = (ctx) => {
    ctx.fillRect(0, 0, 50, 50);
    ctx.scale(2,2);
    ctx.fillRect(50, 0, 50, 50);
  }
複製代碼

image.png

能夠看到不只是目標的長寬發生了縮放,距離原點(0,0)的偏離也出現了相應的縮放。
注意縮放的數值若是爲負數,至關因而對X、Y軸作鏡像的翻轉,且繪製的窗寬的方向也變成了負數

const draw = (ctx) => {
    ctx.fillRect(0, 0, 50, 50); // 寬度爲正數,從左往右計算
    ctx.fillRect(100, 50, -50, 50); // 寬度爲負數,從右往左計算
    ctx.scale(-1,1); // 原點左側爲0到正無窮,右側爲0到負無窮,Y軸沒作縮放
    ctx.fillRect(-50, 100, 50, 50); // 寬度爲正是數,從右往左計算
    ctx.fillRect(-50, 150, -50, 50); // 寬度爲負數,從左往右計算
  }
複製代碼

image.png

移動

translate(x, y)方法接受兩個參數。x 是左右偏移量,y 是上下偏移量。和其餘屬性同樣,一旦設置之後對後續操做都是疊加效應,每次移動是基於上一次移動的基礎之上的,並不會一直相對於原點。

const draw = (ctx) => {
    ctx.fillRect(0, 0, 20, 20);
    ctx.translate(30, 0)
    ctx.fillRect(0, 0, 20, 20);
    ctx.translate(0, 30); // 只平移了Y值,而X值仍然是以前的30
    ctx.fillRect(0, 0, 20, 20);
    ctx.translate(30, 0); // 是在以前(30,0)+(0,30)的基礎上再加(30,0),至關於相對於原點的位移是(60,30)
    ctx.fillStyle = '#1890ff';
    ctx.fillRect(0, 0, 20, 20);
  }
複製代碼

image.png

能夠配合使用save和restore方法來使位移一直相對於原點,更符合咱們的計算

const draw = (ctx) => {
    ctx.save(); // 繪製前保存一個原始的狀態
    ctx.fillRect(0, 0, 20, 20);
    ctx.restore(); // 繪製後還原到原始的狀態

    ctx.save();
    ctx.translate(20, 20)
    ctx.fillRect(0, 0, 20, 20);
    ctx.restore();

    ctx.save();
    ctx.translate(40, 40)
    ctx.fillRect(0, 0, 20, 20);
    ctx.restore();

    ctx.save();
    ctx.translate(60, 60)
    ctx.fillRect(0, 0, 20, 20);
    ctx.restore();
  }
複製代碼

image.png

旋轉

rotate(angle)方法只接受一個參數:旋轉的角度(angle),它是以原點爲中心順時針方向的角度,以 弧度 爲單位的值。

const draw = (ctx) => {
    ctx.lineWidth = 5;
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineTo(100, 0);
    ctx.stroke();

    ctx.save();
    ctx.rotate(2 * Math.PI / 360 * 30)
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineTo(100, 0);
    ctx.stroke();
    ctx.restore();

    ctx.save();
    ctx.rotate(2 * Math.PI / 360 * 60)
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineTo(100, 0);
    ctx.stroke();
    ctx.restore();

    ctx.save();
    ctx.rotate(2 * Math.PI / 360 * 90)
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineTo(100, 0);
    ctx.stroke();
    ctx.restore();
  }
複製代碼

image.png

若是要繞圖形的中心旋轉,須要經過旋轉角度反推計算平移的距離

const draw = (ctx) => {
    ctx.fillRect(0, 0, 40, 40);

    ctx.save();
    ctx.translate(80, 40)
    ctx.globalAlpha = 0.5
    ctx.fillRect(0, 0, 40, 40); // 圖形2, 位移以後的參考座標
    ctx.rotate(2 * Math.PI / 360 * 45)
    ctx.fillStyle = "#1890ff"; // 藍色
    ctx.fillRect(0, 0, 40, 40);
    ctx.restore();

    // 若是想在圖形2的位置中心旋轉,須要動態計算水平偏移座標
    ctx.save();
    ctx.globalAlpha = 0.5
    ctx.fillStyle = "#FFEC3D"; // 黃色
    const R = 20; // 2分之一的邊長
    const r = 60; // 旋轉角度
    const diagonalR = Math.sqrt(Math.pow(R, 2) + Math.pow(R, 2)) // 2分之一的對角線長
    const translateX = 80 + R - diagonalR * Math.sin(2 * Math.PI / 360 * (45 - r));
    const translateY = 40 + R - diagonalR * Math.cos(2 * Math.PI / 360 * (45 - r));
    ctx.translate(translateX, translateY)
    ctx.rotate(2 * Math.PI / 360 * r)
    ctx.fillRect(0, 0, 2 * R, 2 * R);
    ctx.restore();
  }
複製代碼

image.png

形變

  • transform(a, b, c, d, e, f),是將當前的變形矩陣乘上一個基於自身參數的矩陣,其原理涉及座標的幾何變換,正是矩陣善於處理的事情
    • a(m11): 水平方向的縮放,別名m11
    • b(m12): 豎直方向的傾斜偏移,別名m12
    • c(m21): 水平方向的傾斜偏移,別名m21
    • d(m22): 豎直方向的縮放,別名m22
    • e(dx): 水平方向的移動,別名dx
    • f(dy): 豎直方向的移動,別名dy
  • setTransform(a, b, c, d, e, f),是將當前的變形矩陣重置爲單位矩陣,而後用相同的參數調用 transform(a, b, c, d, e, f) 方法;至關因而先取消了當前變形,而後設置爲指定的變形,一步完成
  • resetTransform(),重置當前變形爲單位矩陣,它和調用 setTransform(1, 0, 0, 1, 0, 0) 同樣
  • getTransform(),獲取當前變形矩陣參數,獲得一個對象(包含 a、b、c、d、e、f 等參數),能夠經過這個方法獲取參數來處理部分繪製,好比在變形的基礎上某個圖形不須要縮放,能夠把該圖形的寬高除上相應的a(m11)、d(m22)
相關文章
相關標籤/搜索