HTML Canvas API的使用教程

128 阅读4分钟

提示:还可以查看我的教程:如何将画布打印成数据URL如何将文本写入HTML画布如何在HTML画布中加载图像,以及如何用Node.js和Canvas创建和保存图像

HTML画布是一个HTML标签,<canvas> ,它是一个元素,我们可以使用画布API绘制到其中。

创建一个画布

创建画布就像在一个空白的HTML文件中投放一个<canvas></canvas> ,非常简单。

你在页面中看不到任何东西,因为画布是一个不可见的元素。我们来添加一些边框。

Chrome会自动给body 元素添加一个8px的边框。这就是为什么我们的边框看起来像一个框架,你可以通过设置删除这个边框。

我们现在先保留默认值。

我们的画布现在可以通过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中的第一层元素,上述代码将扩大画布,以适应整个主体。

主体并没有填满整个窗口的大小。为了填充整个页面,我们反而需要使用JavaScript。

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

如果你现在去掉主体的边距,用CSS设置画布的背景,我们就可以用画布填满整个页面,我们就可以开始在上面画画。

如果窗口调整大小,我们也需要重新计算画布的宽度,使用退避装置来避免调用太多次我们的画布调整大小(例如,当你用鼠标移动窗口时,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第二版
  • bitmaprenderer 使用ImageBitmap

基于上下文类型,你可以传递第二个参数给getContext() ,以指定额外的选项。

2d 上下文的情况下,我们基本上有一个参数可以在所有的浏览器中使用,它就是alpha ,一个布尔值,默认为真。如果设置为false,浏览器就知道画布没有透明背景,可以加快渲染速度。

在画布上绘制元素

有了上下文,我们现在可以绘制元素。

我们有几种方法可以做到这一点。我们可以绘制

  • 文本
  • 线条
  • 矩形
  • 路径
  • 图像

对于这些元素中的每一个,我们都可以改变填充、描边、渐变、图案、阴影、旋转、缩放和执行大量的操作。

让我们从最简单的东西开始:一个矩形。

fillRect(x, y, width, height) 方法可以达到这个目的。

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

这将绘制一个100×100像素的黑色矩形,从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`

矩形

你有3个方法。

  • 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 (开始*,结束,左,右,中间)
  • textBaseline (顶部,悬挂,中间,字母*,表意,底部)
  • direction (ltr, rtl, 继承*)

线条

要画一条线,你首先调用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()

另一个例子:在画布上给元素做动画

基于上面的例子,我们用一个循环对元素进行动画处理。每个圆都有自己的 "生命",在画布的边界内移动。当到达边界时,它就会弹回来。

请参阅Flavio Copes(@flaviocopes)在CodePen上发表的《PenHTML Canvas的乐趣与圆圈,非互动》。

我们通过使用 requestAnimationFrame()并在每一帧渲染迭代中略微移动图像。

与画布上的元素互动

下面是上述例子的扩展,让你用鼠标与圆圈互动。

当你在画布上悬停时,你的鼠标附近的项目会增大,当你移动到其他地方时,它们又会恢复正常。

请看Flavio Copes(@flaviocopes)在CodePen上发表的PenHTML Canvas与圆圈的乐趣

它是如何工作的?首先,我使用两个变量跟踪鼠标的位置。

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),定义了我们希望圆圈对鼠标的反应距离。

性能

如果你试图编辑上面的那些项目,并添加一堆更多的圆圈和移动部件,你可能会注意到性能问题。浏览器在渲染带有动画和互动的画布时消耗了大量的能量,所以要注意,以免在性能较差的机器上破坏了体验。

特别是当我试图用表情符号而不是圆圈创造类似的体验时,我遇到了问题,我发现文本需要更多的能量来渲染,因此很快就变得迟钝了。

请看Flavio Copes(@flaviocopes) 在CodePen上发表的PenHTML Canvas与Emojis乐趣

MDN上的这个页面列出了许多性能提示。

结束语

这只是对 Canvas 的可能性的一个介绍,这是一个神奇的工具,你可以用它来在你的网页上创造令人难以置信的体验。