一個用於瀏覽器繪製到屏幕上的方法之一,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 版本 1webgl2
,使用 WebGL 版本 2bitmaprenderer
,與 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 這裡。
更改顏色
使用 fillStyle
和 strokeStyle
屬性來更改任何圖形的填充和描邊顏色。它們接受任何有效的 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)
它們讓您在畫布上寫文本。
x
和 y
是指左下角。
您可以使用畫布的 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 是一個驚人的工具,您可以使用它在網頁上創建令人難以置信的體驗。