一個用於瀏覽器繪製到屏幕上的方法之一,Canvas API 的指南。

提示:還可以查看我的教程 如何將畫布列印到數據URL如何將文本寫入到HTML畫布如何在HTML畫布中加載圖像如何使用Node.js和Canvas創建並保存圖像

HTML Canvas 是一個 HTML 標籤 <canvas>,我們可以使用 Canvas API 在其中繪製。

創建一個畫布

創建一個畫布就像在空白的 HTML 文件中添加一個 <canvas></canvas> 一樣簡單:

在頁面中看不到任何內容,因為畫布是一個不可見的元素。讓我們添加一些邊框:

Chrome 自動給 body 元素添加了 8px 的邊距。這就是為什麼我們的邊框看起來像一個框架,你可以通過設置

body {
 margin: 0;
}

來刪除邊距。我們暫時使用默認值。

現在我們可以使用 DOM 選擇器 API 從 JavaScript 中訪問我們的畫布,可以使用 document.querySelector()

const canvas = document.querySelector('canvas')

更改畫布的背景顏色

在 CSS 中更改背景顏色:

canvas {
 background-color: lightblue;
}

調整畫布大小

在 CSS 中設置寬度和高度:

canvas {
 border: 1px solid black;
 width: 100%;
 height: 100%;
}

這樣,畫布就會擴展到填滿外部元素的尺寸。

如果將畫布作為 HTML 中的一級元素,上面的代碼將使畫布擴展到填滿整個 body。

body 並未填充整個窗口大小。要填滿整個頁面,我們需要使用 JavaScript:

canvas.width = window.innerWidth
canvas.height = window.innerHeight

如果現在刪除 body 的邊距,並使用 CSS 設置畫布的背景,我們可以使用畫布填滿整個頁面並開始在其上繪製:

如果窗口調整大小,我們也需要重新計算畫布的寬度,使用防抖動(debounce)來避免過多調用我們的畫布調整大小的函數(例如,resize 事件可能會多次調用,例如你使用鼠標移動窗口時可能調用數百次):

const debounce = (func) => {
 let timer
 return (event) => {
 if (timer) { clearTimeout(timer) }
 timer = setTimeout(func, 100, event)
 }
}

window.addEventListener('resize', debounce(() => {
 canvas.width = window.innerWidth
 canvas.height = window.innerHeight
}))

從畫布獲取上下文

我們想要在畫布上繪製。

為了做到這一點,我們需要獲取一個上下文:

const c = canvas.getContext('2d')

有些人將內容分配給一個名為 c 的變量,有些人使用 ctx - 這是一種方便的「上下文」的縮寫

getContext() 方法根據傳遞給它的參數返回一個繪製上下文。

有效的值為

  • 2d,我們將使用的值
  • webgl,使用 WebGL 版本 1
  • webgl2,使用 WebGL 版本 2
  • bitmaprenderer,與 ImageBitmap 一起使用

根據上下文類型,您可以將第二個參數傳遞給 getContext() 以指定其他選項。

2d 上下文的情況下,我們基本上可以在所有瀏覽器上使用一個參數,它就是 alpha,一個布爾值,默認為 true。如果設置為 false,瀏覽器知道畫布沒有透明背景,可以加快渲染。

將元素繪製到畫布上

使用上下文,我們現在可以繪製元素。

我們有幾種方法可以這樣做。我們可以繪製:

  • 文本
  • 線條
  • 矩形
  • 路徑
  • 圖像

對於這些元素的每一個,我們都可以改變填充、描邊、漸變、圖案、陰影、旋轉、比例和執行許多操作。

讓我們從最簡單的事情開始:繪製矩形。

fillRect(x, y, width, height) 方法用於此目的:

c.fillRect(100, 100, 100, 100)

這將繪製一個 100x100 像素的黑色矩形,從 x 100 和 y 100 的位置開始:

您可以通過使用 fillStyle() 方法為矩形上色,傳遞任何有效的 CSS 顏色字符串來為其上色:

c.fillStyle = 'white'
c.fillRect(100, 100, 100, 100)

現在您可以創意無限,以此方式繪製許多東西:

for (let i = 0; i < 60; i++) {
 for (let j = 0; j < 60; j++) {
 c.fillStyle = `rgb(${i * 5}, ${j * 5}, ${(i+j) * 50})`
 c.fillRect(j * 20, i * 20, 10, 10)
 }
}

或者

for (let i = 0; i < 60; i++) {
 for (let j = 0; j < 60; j++) {
 c.fillStyle = `rgb(${i * 5}, ${j * 5}, ${(i+j) * 50})`
 c.fillRect(j * 20, i * 20, 20, 20)
 }
}

繪製元素

正如前面提到的,您可以繪製許多東西:

  • 文本
  • 線條
  • 矩形
  • 路徑
  • 圖像

讓我們只看一些,矩形和文本,以便了解事情是如何工作的。您可以在這裡找到您需要的其他所有 API 這裡

更改顏色

使用 fillStylestrokeStyle 屬性來更改任何圖形的填充和描邊顏色。它們接受任何有效的 CSS 顏色,包括字符串和 RGB 計算:

c.strokeStyle = `rgb(255, 255, 255)`
c.fillStyle = `white`

矩形

您有三個方法:

  • clearRect(x, y, width, height)
  • fillRect(x, y, width, height)
  • strokeRect(x, y, width, height)

我們在前一節中看到了 fillRect()strokeRect() 的調用方式類似,但是不是填充矩形,而是只畫出輪廓使用當前的描邊樣式(可以使用 strokeStyle 上下文屬性更改):

const c = canvas.getContext('2d')
for (let i = 0; i < 61; i++) {
 for (let j = 0; j < 61; j++) {
 c.strokeStyle = `rgb(${i * 5}, ${j * 5}, ${(i+j) * 50})`
 c.strokeRect(j * 20, i * 20, 20, 20)
 }
}

clearRect() 將區域設置為透明:

文本

繪製文本與矩形類似。您有兩種方法:

  • fillText(text, x, y)
  • strokeText(text, x, y)

它們讓您在畫布上寫文本。

xy 是指左下角。

您可以使用畫布的 font 屬性更改字體家族和大小:

c.font = '148px Courier New'

還有其他相關的文本屬性可以更改(* = 默認值):

  • textAlign(start*,end,left,right,center)
  • textBaseline(top,hanging,middle,alphabetic*,ideographic,bottom)
  • direction(ltr,rtl,inherit*)

線條

要繪製一條線,您需要首先調用 beginPath() 方法,然後使用 moveTo(x, y) 提供一個起始點,然後使用 lineTo(x, y) 使線到達這些新坐標。最後,調用 stroke()

c.beginPath()
c.moveTo(10, 10)
c.lineTo(300, 300)
c.stroke()

線條的顏色將根據 c.strokeStyle 屬性值進行上色。

一個更複雜的例子

此代碼創建了一個生成 800 個圓形的畫布:

每個圓都完全包含在畫布中,其半徑是隨機的。

每次調整窗口大小時,元素會重新生成。

您可以在 Codepen 上玩耍

const canvas = document.querySelector('canvas')
canvas.width = window.innerWidth
canvas.height = window.innerHeight
const c = canvas.getContext('2d')

const circlesCount = 800

const colorArray = [
 '#046975',
 '#2EA1D4',
 '#3BCC2A',
 '#FFDF59',
 '#FF1D47'
]

const debounce = (func) => {
 let timer
 return (event) => {
 if (timer) { clearTimeout(timer) }
 timer = setTimeout(func, 100, event)
 }
}

window.addEventListener('resize', debounce(() => {
 canvas.width = window.innerWidth
 canvas.height = window.innerHeight

 init()
}))

const init = () => {
 for (let i = 0; i < circlesCount; i++) {
 const radius = Math.random() * 20 + 1
 const x = Math.random() * (innerWidth - radius * 2) + radius
 const y = Math.random() * (innerHeight - radius * 2) + radius
 const dx = (Math.random() - 0.5) * 2
 const dy = (Math.random() - 0.5) * 2

 const circle = new Circle(x, y, dx, dy, radius)
 circle.draw()
 }
}

const Circle = function(x, y, dx, dy, radius) {
 this.x = x
 this.y = y
 this.dx = dx
 this.dy = dy
 this.radius = radius
 this.minRadius = radius
 this.color = colorArray[Math.floor(Math.random() * colorArray.length)]

 this.draw = function() {
 c.beginPath()
 c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false)
 c.strokeStyle = 'black'
 c.stroke()
 c.fillStyle = this.color
 c.fill()
 }
}

init()

另一個例子:對畫布上的元素進行動畫處理

基於上面的例子,我們使用循環來使元素動起來。每個圓都有自己的 “生命”,在畫布邊界內移動。當到達邊界時,它會反彈:

CodePen 上看到該例子。

這是通過使用 requestAnimationFrame() 並在每個幀渲染迭代中稍微移動圖像來實現的。

與畫布上的元素互動

這是上面的例子,擴展為讓您使用鼠標與圓互動。

當您將鼠標懸停在畫布上時,靠近鼠標的項目將增大,當您移動到其他地方時,它們將恢復正常:

CodePen 上看到該例子。

它是如何工作的?首先,我使用兩個變量來跟踪鼠標位置:

let mousex = undefined
let mousey = undefined

window.addEventListener('mousemove', (e) => {
 mousex = e.x
 mousey = e.y
})

然後,我們在 Circle 的 update() 方法中使用這些變量來確定半徑是否應該增加(或減小):

if (mousex - this.x < distanceFromMouse && mousex - this.x > -distanceFromMouse && mousey - this.y < distanceFromMouse && mousey - this.y > -distanceFromMouse) {
 if (this.radius < maxRadius) this.radius += 1
} else {
 if (this.radius > this.minRadius) this.radius -= 1
}

distanceFromMouse 是以像素為單位的值(設為 200), 定義了鼠標距離圓的多遠的距離。

性能

如果您嘗試編輯上面的項目並添加更多圓和移動的元素,您可能會注意到性能問題。瀏覽器在渲染帶有動畫和互動的畫布時消耗了大量的能量,因此要注意在運行性能低於您的計算機的機器上不要損壞體驗。

特別是當我試圖創建一個使用表情符號而不是圓的類似體驗時,我遇到了問題,發現文本在渲染時需要更多的能量,因此它很快就變得不流暢。

在這個 MDN 頁面 中列舉了許多性能提示。

總結

這僅僅是對 Canvas 的可能性的介紹,Canvas 是一個驚人的工具,您可以使用它在網頁上創建令人難以置信的體驗。