Canvas入门:掌握基础功能

276 阅读12分钟

这是个人写的一篇针对canvas的基础功能的教程,学习完可以对canvas有一个初步的理解,希望对大家有所帮助。

基本用法

canvas元素

  • 像普通的dom元素一样,支持设置idclass等等信息。
  • canvas标签不可缺省</canvas>
  • 支持设置自身的widthheight(单位不添加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)点。

image.png

绘制矩形

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()

image.png

绘制路径

多个点组成一条路径,多个路径组成一个图形。

通过路径绘制图形需要以下几个步骤:

  1. 创建路径的起始点。
  2. 使用画图命令画路径。
  3. 封闭路径。
  4. 填充或描边刚刚绘制的路径。

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()

image.png

圆弧

canvas中我们绘制圆弧或者圆需要使用:
arc(x, y, radius, startAngle, endAngle, anticlockwise)

  • 画一个以(x,y)为圆心的以 radius 为半径的圆弧(圆),从 startAngle 开始到 endAngle 结束,按照 anticlockwise 给定的方向(默认为顺时针)来生成。
  • (x, y)为圆心点坐标。
  • radius为半径
  • startAngleendAngle为起始弧度与结束弧度,弧度 = 角度 * π / 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)
}

image.png

色彩

  • 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()

image.png

线型

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:将渐变对象赋予fillStylestrokeStyle
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)

image.png

图案样式

  • ctx.createPattern(image, type)
    • image: 贴图, Image对象或另一个canvas对象。
    • type: 重复方式,repeat, repeat-x, repeat-y, no-repeat

提示:与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)
}

image.png

阴影

  • 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)

image.png

这篇文章是参考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``right center

  • 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)

image.png

预测量文本宽度

  • ctx.measureText(text)

通过该方法可以获取一个textMetrics对象,该对象上可以获取例如witdh等一系列文本信息。

image.png

绘制图片

首先我们需要准备一些图片资源,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画布的坐标与要渲染的图片指定的尺寸。这里我们可以配合下图进行理解。

image.png
以下是三种入参分别的效果图:

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)
}

image.png

状态保存与恢复

因为我们可能会操作canvas的各种样式,但操作完后,我们可能需要还原这些样式。举个例子,在某些情况下我们希望设置填充色fillColor为红色,但填充完毕后,我们希望还原成初始状态,就需要重新设置fillColor为初始值,这一反复的操作比较繁琐且容易遗漏。
于是canvas提供了saverestore
保存
恢复当前状态,这两个方法类似一个栈操作,当调用save方法后,将当前canvas的状态保存到栈中,当调用restore时,从栈中取出最后一个保存的状态,将canvas还原成该状态。

  • save()

保存画布当前的状态

  • restore()

恢复上一个保存的状态

在操作这些属性前,保存一下当前状态是一个很好地习惯,支持保存的状态如下:
strokeStylefillStyleglobalAlphalineWidthlineCaplineJoinmiterlimitlineDashOffsetshadowOffsetXshadowBlurshadowColorglobalCompositeOperationfonttextAligntextBaselinedirectionimageSmoothingEnabledtranslaterotatescaletransform裁切路径(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()

image.png

旋转

  • 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()
}

image.png

缩放

  • 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()

image.png
以下是一个为负数情况下的反转的例子:

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)

image.png

变形

  • 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()

image.png

参考文档

MDN:Canvas 教程