这是个人写的一篇针对canvas的基础功能的教程,学习完可以对canvas有一个初步的理解,希望对大家有所帮助。
基本用法
canvas元素
- 像普通的dom元素一样,支持设置
id、class等等信息。 canvas标签不可缺省</canvas>- 支持设置自身的
width、height(单位不添加px)。 - 允许在内部添加一些元素,当浏览器支持不
canvas标签时,会显示这些内部的元素,而当浏览器支持canvas标签时,会忽律这些元素,正常显示。
<canvas id="canvas" width="200" height="200">
您的浏览器不支持canvas
</canvas>
渲染上下文
canvas元素创造了一个画布,它公开了一个或多个渲染上下文,渲染上下文可以用以绘制或展示内容。canvas元素可以通过getContext()方法获取渲染上下文。
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
绘制形状
栅格
canvas元素,默认被网格覆盖,即画布栅格,类似于一个坐标轴,左上角默认为(0,0)点。
绘制矩形
canvas只支持两种形式的图形绘制:矩形和路径(由一系列点连成的线段)。所有其他类型的图形都是通过一条或者多条路径组合而成的。
fillRect(x, y, width, height):绘制一个填充的矩形。strokeRect(x, y, width, height):绘制一个矩形的边框。clearRect(x, y, widh, height):清除指定的矩形区域,然后这块区域会变的完全透明。
function drawRect() {
const canvas = document.querySelector('#canvas')
const ctx = canvas.getContext('2d')
const width = 500
const height = 500
canvas.width = width
canvas.height = height
// 填充
ctx.fillRect(0,0, 500, 500)
// 清除
ctx.clearRect(100,100, 300, 300)
// 绘制边框
ctx.strokeRect(150, 150, 200, 200)
}
drawRect()
绘制路径
多个点组成一条路径,多个路径组成一个图形。
通过路径绘制图形需要以下几个步骤:
- 创建路径的起始点。
- 使用画图命令画路径。
- 封闭路径。
- 填充或描边刚刚绘制的路径。
canvas提供了以下几个函数去实现这些功能:
beginPath():新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径。moveTo(x, y):将笔触移动到指定的坐标 x 以及 y 上。lineTo(x, y):绘制一条从当前位置到指定 x 以及 y 位置的直线。closePath():闭合路径之后图形绘制命令又重新指向到上下文中。stroke():通过线条来绘制图形轮廓。fill():通过填充路径的内容区域生成实心的图形。
要注意以下几点:
- 大部分情况下在调用
beginPath之后会直接调用moveTo将画笔移动到起始的坐标。 closePath不是必须的。这个方法会绘制一条当前点到开始点的直线,如果线路已经闭合,则改函数什么也不做。- 调用
fill是会自动闭合线路,但stoke则不会闭合线路。
function drawTriangle() {
const canvas = document.querySelector('#canvas')
const ctx = canvas.getContext('2d')
const width = 600
const height = 600
canvas.width = width
canvas.height = height
// 填充一个三角形
ctx.beginPath()
ctx.moveTo(0,0) // 设置起始点(0,0)
ctx.lineTo(100, 100) // 绘制一条从(0, 0)到(100, 100)的虚拟线
ctx.lineTo(0, 200) // 绘制一条从(100, 100)到(0, 200)的虚拟线
ctx.closePath() // 结束路径,自动闭合路线从(0, 200)到(0, 0)
ctx.fill() // 填充
// 描边一个不闭合的三角形
ctx.beginPath()
ctx.moveTo(200, 0) // 设置起始点(200, 0)
ctx.lineTo(300, 100) // 绘制一条从(200, 0)到(300, 100)的虚拟线
ctx.lineTo(200, 200) // 绘制一条从(300, 100)到(200, 200)的虚拟线
ctx.stroke() // 描边,因为是描边,所以不会自动线路闭合,如果调用fill,则会自动线路闭合回到(200, 0)
}
drawTriangle()
圆弧
在canvas中我们绘制圆弧或者圆需要使用:arc(x, y, radius, startAngle, endAngle, anticlockwise)
- 画一个以(x,y)为圆心的以 radius 为半径的圆弧(圆),从 startAngle 开始到 endAngle 结束,按照 anticlockwise 给定的方向(默认为顺时针)来生成。
(x, y)为圆心点坐标。radius为半径startAngle与endAngle为起始弧度与结束弧度,弧度 = 角度 * π / 180。anticlockwise绘制方向,默认false顺时针,为true的画,则逆时针。
以下是绘制一个吃豆人的样式,这里使用到了Class类,通过渲染各种不同弧度、半径、圆心的圆,来实现了一个基本的效果。
const canvas = document.querySelector('#canvas')
const ctx = canvas.getContext('2d')
const width = 500
const height = 500
canvas.width = width
canvas.height = height
class PacMan {
constructor(ctx) {
this.ctx = ctx
this.renderBody()
this.renderEye()
}
renderBody() {
const ctx = this.ctx
const center = { x: 200, y: 200 } // 圆心
const r = 100 // 半径
const startAngle = Math.PI * 30 / 180 // 起始弧度(对应的30的角度)
const endAngle = Math.PI * 330 / 180 // 结束弧度(对应的330的角度)
ctx.beginPath()
ctx.moveTo(center.x, center.y)
ctx.arc(center.x, center.y, r, startAngle, endAngle)
ctx.closePath()
ctx.fill()
}
renderEye() {
const ctx = this.ctx
const center = { x: 220, y: 150 } // 圆心
const r = 10 // 半径
const startAngle = 0 // 起始弧度(对应的0的角度)
const endAngle = Math.PI * 2 // 结束弧度(对应的360的角度, 2π就是圆)
ctx.beginPath()
ctx.moveTo(center.x, center.y)
ctx.arc(center.x, center.y, r, startAngle, endAngle)
ctx.closePath()
ctx.fillStyle = 'white'
ctx.fill()
}
}
class Pac {
constructor(ctx, x, y) {
this.x = x
this.y = y
this.r = 10
this.ctx = ctx
this.render()
}
render() {
const {x, y, r, ctx} = this
ctx.beginPath()
ctx.moveTo(x, y)
ctx.arc(x, y, r, 0, Math.PI * 2)
ctx.closePath()
ctx.fillStyle = 'black'
ctx.fill()
}
}
new PacMan(ctx)
for(let i = 1; i<=4; i++) {
new Pac(ctx, 200 + 40 * i, 200)
}
色彩
ctx.fillStyle = color:设置填充色ctx.strokeStyle = color:设置轮廓颜色ctx.globalAlpha = aplha:设置整体canvas的透明度
默认情况下color都为黑色,支持任意符合css3颜色标准的颜色。 globalAlpha设置的透明度会与rgba进行叠加。
ctx.beginPath()
ctx.arc(25, 25, 25,0, Math.PI, true)
ctx.lineTo(0, 100)
ctx.arc(25, 100, 25, Math.PI, 0, true)
ctx.lineTo(50, 25)
ctx.stroke()
ctx.closePath()
ctx.beginPath()
ctx.fillStyle = 'rgb(255, 0, 0)'
ctx.arc(25, 25, 15, 0, 2 * Math.PI)
ctx.fill()
ctx.closePath()
ctx.beginPath()
ctx.fillStyle = 'rgb(255, 255, 0)'
ctx.arc(25, 65, 15, 0, 2 * Math.PI)
ctx.fill()
ctx.closePath()
ctx.beginPath()
ctx.fillStyle = 'rgb(0, 255, 0)'
ctx.arc(25, 105, 15, 0, 2 * Math.PI)
ctx.fill()
ctx.closePath()
线型
ctx.lineWidth = value:设置线条宽度ctx.lineCap = type:设置线条末端样式
- butt:默认
- round:圆角
- square:方形
ctx.lineJoin = type:设置两条线连接处样式
- round:圆角
- bevel:裁切
- miter:默认
ctx.miterLimt = value
定外延交点与连接点的最大距离来设定外延交点与连接点的最大距离,如果交点距离大于此值,连接效果会变成了 bevel。ctx.setLineDash(segments)
设置虚线样式,segments接受一个数组,来指定线段与间隙的交替;ctx.lineDashOffset
设定虚线的起始偏移量
渐变
canvas渐变分为两种,一种是线性渐变,另一种是径向渐变
ctx.createLinearGradient(x1, y1, x2, y2):创建线性渐变对象,(x1, y1)为起始点, (x2, y2)为结束点。ctx.createRadialGradient(x1, y1, r1, x2, y2, r2): 创建径向渐变对象,(x1, y1)为第一个圆的原点, r1为半径,(x2, y2)为第二个圆的原点,r2为半径。gradient.addColorStop(position, color):上色, position必须是一个0~1之间的小数,表示位置。ctx.fillStyle = gradient:将渐变对象赋予fillStyle或strokeStyle。
const linearGradient = ctx.createLinearGradient(0, 0, 100, 0)
linearGradient.addColorStop(0, '#fff')
linearGradient.addColorStop(1, '#000')
ctx.fillStyle = linearGradient
ctx.fillRect(0, 0, 100, 100)
const radialGradient = ctx.createRadialGradient(300, 300, 100, 300, 300, 50)
radialGradient.addColorStop(0, '#fff')
radialGradient.addColorStop(1, '#000')
ctx.fillStyle = radialGradient
ctx.fillRect(200, 200, 300, 300)
图案样式
ctx.createPattern(image, type)- image: 贴图,
Image对象或另一个canvas对象。 - type: 重复方式,
repeat, repeat-x, repeat-y, no-repeat
- image: 贴图,
提示:与drawImage不同的是,image需要等待加载完毕后,才可以进行使用
const src = 'https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fb0ef7a792544a94bb715b9a93d13a7d~tplv-k3u1fbpfcp-zoom-1.image'
const image = new Image()
image.src = src
image.onload = () => {
const imagePattern = ctx.createPattern(image, 'no-repeat')
ctx.fillStyle = imagePattern
ctx.fillRect(0, 0, 300, 300)
}
阴影
ctx.shadowOffsetX = float:控制阴影在X轴的延伸距离,正左负右。ctx.shadowOffsetY = float:控制阴影在Y轴的延伸距离,正下负上。ctx.shadowBlur = float:设置阴影的模糊程度ctx.shadowColor = color:阴影颜色
ctx.shadowOffsetX = 10
ctx.shadowOffsetY = 10
ctx.shadowBlur = 10
ctx.shadowColor = 'black'
ctx.fillStyle = 'red'
ctx.fillRect(0, 0, 100, 100)
这篇文章是参考mdn官方文档进行学习,并按照我个人的一些想法,对canvas的绘制文本与图片的一些整理,希望对像我一样希望学习canvas但又不知道如何下手的同学有所帮助,如果有错误也希望大家指出。
绘制文本
ctx.fillText(text, x, y [, maxWidth])
在指定(x, y)位置绘制文本。
ctx.strokeText(text, x, y [, maxWidth)
在指定(x, y)绘制文本边框。
设置文本样式
ctx.font = value
设置文本样式,与
css font相同, 默认10px sans-serif。
ctx.textAlign = value
设置文本对齐方式,可选值包括
start``end``left``rightcenter
ctx.textBaseline = value
设置文本基准对齐方式,可选值包括
top``hanging``middle``alphabetic``ideographic``bottom
ctx.direction = value
设置文本对齐方向,可选值包括
ltr``rtl``inherit
以下是textBaseline的各种不同效果:
const canvas = document.querySelector('#canvas')
const ctx = canvas.getContext('2d')
canvas.width = 800
canvas.height = 800
ctx.beginPath()
ctx.moveTo(0, 100)
ctx.lineTo(800, 100)
ctx.closePath()
ctx.stroke()
ctx.font = '36px serif'
ctx.fillText('textBaseline效果:',0, 50)
ctx.font = '24px serif'
ctx.fillText('alphabetic', 0, 100)
ctx.textBaseline = 'top'
ctx.fillText('top', 100, 100)
ctx.textBaseline = 'hanging'
ctx.fillText('hanging', 150, 100)
ctx.textBaseline = 'middle'
ctx.fillText('middle', 250, 100)
ctx.textBaseline = 'ideographic'
ctx.fillText('ideographic', 350, 100)
ctx.textBaseline = 'bottom'
ctx.fillText('bottom', 500, 100)
预测量文本宽度
ctx.measureText(text)
通过该方法可以获取一个
textMetrics对象,该对象上可以获取例如witdh等一系列文本信息。
绘制图片
首先我们需要准备一些图片资源,canvas绘制图片可以使用一下几个资源作为图片资源:
HTMLImageElement
由
Image构造出的对象,或者<img>元素
HTMLVideoElement
<video>元素,当前帧作为图像
HTMLCanvasElement
允许使用另一个
canvas元素
ImageBitmap
高性能的位图
需要注意的是:
- 若需要请求跨域的图片资源,需要资源允许跨域。
- 请确保图片资源加载完成
load,或视频资源canplay
准备好后,我们即可使用drawImage方法,提供了以下三种形式的入参:
ctx.drawImage(image, x, y)
渲染图片资源,(x, y) 是
canvas上的起始坐标点,尺寸是图片自身的原本尺寸。
ctx.drawImage(image, x, y, width, height)
渲染图片资源,(x, y) 是
canvas上的起始坐标点,我们指定了图片大小尺寸。
ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
渲染图片资源,切割图片,sx, sy, sWidth, sHeight,都是图片的坐标与尺寸,dx, dy, dWidth, dHeight,为canvas画布的坐标与要渲染的图片指定的尺寸。这里我们可以配合下图进行理解。
以下是三种入参分别的效果图:
const canvas = document.querySelector('#canvas')
const ctx = canvas.getContext('2d')
canvas.width = 1200
canvas.height = 1200
const image = new Image()
image.src = 'https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/52c22be10d96454e939d5b420e3f9c6c~tplv-k3u1fbpfcp-no-mark:160:160:160:120.awebp?'
image.onload = () => {
// 在(0,0)点渲染原本尺寸的图片
ctx.drawImage(image, 0, 0)
// 在(100,100)点渲染,缩放成160*100的图片
ctx.drawImage(image, 160, 0, 100, 100)
// 基于图片自身的(0,0)点,切割50*50的部分,并渲染在canvas画布的(0, 300)位置,缩放成300*300的图片
ctx.drawImage(image, 0, 0, 50, 50, 300, 0, 300, 300)
}
状态保存与恢复
因为我们可能会操作canvas的各种样式,但操作完后,我们可能需要还原这些样式。举个例子,在某些情况下我们希望设置填充色fillColor为红色,但填充完毕后,我们希望还原成初始状态,就需要重新设置fillColor为初始值,这一反复的操作比较繁琐且容易遗漏。
于是canvas提供了save和restore来保存与恢复当前状态,这两个方法类似一个栈操作,当调用save方法后,将当前canvas的状态保存到栈中,当调用restore时,从栈中取出最后一个保存的状态,将canvas还原成该状态。
save()
保存画布当前的状态
restore()
恢复上一个保存的状态
在操作这些属性前,保存一下当前状态是一个很好地习惯,支持保存的状态如下:strokeStyle、fillStyle、globalAlpha、lineWidth、lineCap、lineJoin、miterlimit、lineDashOffset、shadowOffsetX、shadowBlur、shadowColor、globalCompositeOperation、font、textAlign、textBaseline、direction、imageSmoothingEnabled、translate、rotate、scale、transform、裁切路径(clipping path)
移动
translate(x, y)
将canvas的原点移动到一个不同的位置,x为左右偏移量,y为上下偏移量
通过调用translate方法,我们可以将原点移动到我们想要的位置,这在绘制一些图形的时候会非常有用,我们不必再基于左上角(0,0)点,作为基准点去考虑绘制图形的(x, y)坐标。
以下是一个通过将原点移动到canvas中心点(0, 0),进行绘制的一个图形,通过计算各个点的(x, y)坐标,实现一个圆。
const canvas = document.querySelector('#canvas')
const width = 500
const height = 500
canvas.width = 500
canvas.height = 500
const ctx = canvas.getContext('2d')
ctx.strokeRect(0, 0, width, height)
ctx.save()
ctx.font = '16px san-self'
ctx.textAlign = 'center'
ctx.translate(width/2, height/2)
for(let i = 1; i <= 12; i++) {
ctx.save()
const r = 100
const angle = i * 30 // 每个点的角度
const radian = angle * Math.PI / 180 // 每个点的弧度
const x = Math.cos(radian) * r
const y = Math.sin(radian) * r
ctx.fillText('棒', x, y)
}
ctx.restore()
旋转
rotate(angle)
以原点为圆心进行顺时针旋转,以弧度为单位。
以下是一个通过translate配合rotate绘制一个图形的例子,我们不在计算(x, y)的坐标,他们的坐标始终为(0, 半径r),通过旋转,我们围绕360,实现了一个圆。
const ctx = canvas.getContext('2d')
ctx.strokeRect(0, 0, width, height)
ctx.save()
ctx.font = '16px san-self'
ctx.textAlign = 'center'
ctx.translate(width/2, height/2)
for(let i = 1; i <= 12; i++) {
ctx.save()
const r = 100
const angle = i * 30 // 每个点的角度
const radian = angle * Math.PI / 180 // 每个点的弧度
ctx.rotate(radian)
ctx.fillText('棒', 0, r)
ctx.restore()
}
缩放
scale(x, y)
将canvas进行缩放,x为水平缩放因子,y为垂直缩放因子,默认值为1,若大于1则放大,若小于1则缩小;可为负数,若为负数的情况下,则会进行反转
缩放的本质是对canvas的单位进行放大或缩小,canvas默认单位为1像素,若设置因子为0.5,1个单位由原先的1像素变为0.5像素,图形便缩小了0.5倍。
以下一个放大与缩小的例子:
// 放大2倍
ctx.save()
ctx.fillStyle = 'blue'
ctx.scale(2, 2)
ctx.fillRect(0, 0, width/2, height/2)
ctx.restore()
// 常规
ctx.save()
ctx.fillStyle = 'red'
ctx.fillRect(0, 0, width/2, height/2)
ctx.restore()
// 缩小
ctx.save()
ctx.fillStyle = 'green'
ctx.scale(0.5, 0.5)
ctx.fillRect(0, 0, width/2, height/2)
ctx.restore()
以下是一个为负数情况下的反转的例子:
ctx.save()
ctx.font = '50px san-self'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.translate(width/2, height/2)
ctx.fillText('你好', 30, 30)
ctx.save()
ctx.scale(-1, -1)
ctx.fillText('你好', 30, 30)
变形
transform(a, b, c, d, e, f)
各参数含义如下:
a:水平方向缩放b:竖直方向的倾斜偏移c:水平方向的倾斜偏移d:竖直方向的缩放e:水平方向的移动f:竖直方向的移动
裁切
正常情况下,我们在canvas上绘制图形,通过坐标与大小等方式进行绘制,但假设我们绘制了一个图形,之后想针对这个图形进行修改,但却不想对这个图形以外的地方产生影响。
clip()
将构建的路径转换为裁切路径,默认情况下,canvas有一个和他一样大的裁切路径。
以下的案例,我们新建一个圆形与方形的路径,并转换成了裁切路径,之后填充的颜色,只对该裁切的路径产生影响。
const canvas = document.querySelector('#canvas')
const width = 300
const height = 300
canvas.width = width
canvas.height = height
const ctx = canvas.getContext('2d')
ctx.fillStyle = 'blue'
ctx.fillRect(0,0, width, height)
ctx.save()
ctx.arc(width/2, height/2, 50, 0, 2 * Math.PI)
ctx.clip()
ctx.fillStyle = 'red'
ctx.fillRect(0, 0, width, height)
ctx.restore()
ctx.save()
ctx.beginPath()
ctx.moveTo(0, 0)
ctx.lineTo(100, 0)
ctx.lineTo(100, 100)
ctx.lineTo(0, 100)
ctx.closePath()
ctx.clip()
ctx.fillStyle = 'green'
ctx.fillRect(0, 0, width, height)
ctx.restore()