提示:还可以查看我的教程:如何将画布打印成数据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版本1webgl2使用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。
改变颜色
使用fillStyle 和strokeStyle 属性来改变任何图形的填充和描边颜色。它们接受任何有效的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 的可能性的一个介绍,这是一个神奇的工具,你可以用它来在你的网页上创造令人难以置信的体验。