Skip to content

Canvas 入门

本文内容来自

Canvas 是什么

  • Canvas 中文名叫 “画布”,是 HTML5 新增的一个标签。
  • Canvas 允许开发者通过 JS在这个标签上绘制各种图案。
  • Canvas 拥有多种绘制路径、矩形、圆形、字符以及图片的方法。
  • Canvas 在某些情况下可以 “代替” 图片。
  • Canvas 可用于动画、游戏、数据可视化、图片编辑器、实时视频处理等领域。

Canvas 和 SVG 的区别

CanvasSVG
用JS动态生成元素(一个HTML元素)用XML描述元素(类似HTML元素那样,可用多个元素来描述一个图形)
位图(受屏幕分辨率影响)矢量图(不受屏幕分辨率影响)
不支持事件支持事件
数据发生变化需要重绘不需要重绘

前置知识

canvas 宽高

我们知道 canvas 是 HTML5 新增的一个标签。那么一个标签盒子基本都会有自己的宽高属性。而 canvas 的默认宽高为300px 150px。

我们可以通过内嵌方式设置 canvas 宽高

html
<canvas width="600" height="400"></canvas>

注意 canvas 宽高不能通过其它 css 方式设置

  1. 使用其它方式设置的宽高只是将 canvas 拉伸变形
  2. 使用 js 获取 canvas 的宽高,此时返回的是 canvas 的默认值。

兼容性

暂时只有 IE 9 以上才支持 canvas 。但好消息是 IE 已经有自己的墓碑了。

canvas 的坐标系

Canvas 使用的是 W3C 坐标系 ,也就是遵循我们屏幕、报纸的阅读习惯,从上往下,从左往右。

canvas

直线

画线

canvas 想要实现直线效果需用到下面三个 api:

  • moveTo(x1, y1):起点坐标 (x, y)
  • lineTo(x2, y2):下一个点的坐标 (x, y)
  • stroke():将所有坐标用一条线连起来
  1. 画一条直线
js
    const cnv = document.getElementById('cvs')
    const cxt = cnv.getContext('2d')

    // 绘制直线
    cxt.moveTo(50, 100) // 起点坐标
    cxt.lineTo(200, 50) // 下一个点的坐标
    cxt.stroke() // 将上面的坐标用一条线连接起来

canvas

  1. 画多条直线
js
cxt.moveTo(20, 100)
cxt.lineTo(200, 100)
cxt.stroke()

cxt.moveTo(20, 120.5)
cxt.lineTo(200, 120.5)
cxt.stroke()

canvas

仔细观察一下,为什么两条线的粗细不一样的?

明明使用的方法都是一样的,只是第二条直线的 Y轴 的值是有小数点。

这是因为默认情况下 canvas 会将线条的中心点和像素的底部对齐,所以会导致显示效果是 2px 和非纯黑色问题。
canvas

线的中心点会和画布像素点的底部对齐,所以会线中间是黑色的,但由于一个像素就不能再切割了,所以会有半个像素被染色,就变成了浅灰色。

所以如果你设置的 Y轴 值是一个整数,就会出现上面那种情况。

线条样式

  • lineWidth:线的粗细
  • strokeStyle:线的颜色
  • lineCap:线帽:默认: butt; 圆形: round; 方形: square
  • lineJoin:拐角样式:尖角:miter(默认); 圆角: round; 斜角: bevel
js
// 绘制直线
cxt.moveTo(50, 50)
cxt.lineTo(200, 50)
// 修改直线的宽度
cxt.lineWidth = 20
// 修改直线的颜色
cxt.strokeStyle = 'pink'
// 修改直线两端样式
cxt.lineCap = 'round'
cxt.stroke()

canvas

虚线

使用 setLineDash([]) 方法可以将线条设置成虚线。
虚线分3种情况

  • 只传1个值 [10]实线与空白都是 10px
  • 有2个值 [10, 20]实线是 10px, 空白 20px
  • 有3个以上的值 [10, 20, 5]实现和空白按顺序使用[]里面的值(循环使用)
js
    cxt.lineWidth = 20
    cxt.strokeStyle = 'pink'

    cxt.moveTo(50, 30)
    cxt.lineTo(200, 30)
    cxt.setLineDash([10]) // 只传1个参数,实线与空白都是 10px
    cxt.stroke()

    cxt.beginPath()
    cxt.moveTo(50, 70)
    cxt.lineTo(200, 70)
    cxt.setLineDash([10, 20]) // 2个参数,此时,实线是 10px, 空白 20px
    cxt.stroke()

    cxt.beginPath()
    cxt.moveTo(50, 110)
    cxt.lineTo(200, 110)
    cxt.setLineDash([10, 20, 5]) // 传3个以上的参数,此例:10px实线,20px空白,5px实线,10px空白,20px实线,5px空白 ……

    cxt.stroke()

canvas

新开路径

  • beginPath()
    在绘制多条线段的时,如果想让后面线段样式不影响前面的线段,就需要开辟新路径。

但是事实上仅靠 beginPath 并不能实现样式隔离

js
    // 第一条线
    cxt.moveTo(20, 100)
    cxt.lineTo(200, 100)
    cxt.lineWidth = 10
    cxt.strokeStyle = 'pink'
    cxt.stroke()

    // 第二条线
    cxt.beginPath() // 重新开启一个路径
    cxt.moveTo(20, 120.5)
    cxt.lineTo(200, 120.5)
    cxt.stroke()

canvas
第二条线我们虽然开辟了新路径,但是还是继承了前面线条的样式。

所以想要实现样式互不影响,必须满足两个条件:

  1. 使用 beginPath 开辟新路径
  2. 设置新的样式
js
     // 第一条线
    cxt.moveTo(20, 100)
    cxt.lineTo(200, 100)
    cxt.lineWidth = 10
    cxt.strokeStyle = 'pink'
    cxt.stroke()

    // 第二条线
    cxt.beginPath() // 重新开启一个路径
    cxt.moveTo(20, 120.5)
    cxt.lineTo(200, 120.5)
    cxt.lineWidth = 4
    cxt.strokeStyle = 'red'
    cxt.stroke()

canvas

TIP

新开路径的线条如果不设置样式,就会受到前一条影响。如果后面设置样式而不开新路径,那么就会全部以后面设置的为主。

闭合路径

  • closePath() 闭合路径有以下两个作用:
  1. 让线条自动闭合,
  2. 让闭合的连接处更加顺其自然

比如画一个三角形:
不使用闭合路径

js
    cxt.moveTo(50, 30)
    cxt.lineTo(200, 30)
    cxt.lineTo(200, 140)
    cxt.lineTo(50, 30) // 闭合

    cxt.stroke()

canvas
使用闭合路径

js
    cxt.moveTo(50, 30)
    cxt.lineTo(200, 30)
    cxt.lineTo(200, 140)
    cxt.closePath()

    cxt.stroke()

canvas

矩形

线性矩形

  • strokeStyle:设置描边的属性(颜色、渐变、图案)
  • strokeRect(x, y, width, height):描边矩形(x和y是矩形左上角起点;width 和 height 是矩形的宽高)
  • strokeStyle 必须写在 strokeRect() 前面,不然样式不生效。
js
    cxt.strokeStyle = 'pink'
    cxt.strokeRect(30, 30, 200, 80)

canvas

填充矩形

  • fillStyle:设置描边的属性(颜色、渐变、图案)
  • fillRect(x, y, width, height):描边矩形(x和y是矩形左上角起点;width 和 height 是矩形的宽高)
  • fillStyle 必须写在 strokeRect() 前面,不然样式不生效。
js
    cxt.fillStyle  = 'pink'
    cxt.fillRect(30, 30, 200, 80)

canvas

TIP

同时使用 strokeRect() 和 fillRect() 可以实现矩形描边效果

rect() 生成矩形

strokeRect() 和 fillRect() 这两个方法调用后会立即绘制;rect() 方法被调用后,不会立刻绘制矩形,而是需要调用 stroke() 或 fill() 辅助渲染。

js
    cxt.strokeStyle = 'red'
    cxt.fillStyle = 'pink'
    cxt.rect(30, 30, 200, 80)
    cxt.stroke()
    cxt.fill()

canvas

清空矩形

使用 clearRect() 方法可以清空指定区域。

js
clearRect(x, y, width, height)

其语法和创建 cxt.rect() 差不多。

js
    cxt.fillStyle = 'pink' // 设置填充颜色
    cxt.fillRect(30, 30, 200, 80) // 填充矩形

    cxt.clearRect(40, 40, 100, 30) // 清空矩形

canvas

画圆

绘制圆形的方法是 arc()。

arc(x, y, r, sAngle, eAngle,counterclockwise)
  • x 和 y: 圆心坐标
  • r: 半径
  • sAngle: 开始角度
  • eAngle: 结束角度
  • counterclockwise: 绘制方向(true: 逆时针; false: 顺时针),默认 false

实际开发中,为了让自己或者别的开发者更容易看懂弧度的数值,1°应该写成 Math.PI / 180。

  • 100°: 100 * Math.PI / 180
  • 110°: 110 * Math.PI / 180
  • 241°: 241 * Math.PI / 180
js
    cxt.beginPath()
    cxt.arc(150, 80, 60, 0, 360 * Math.PI / 180)
    cxt.closePath()

    cxt.stroke()

canvas

TIP

注意:绘制圆形之前,必须先调用 beginPath() 方法!!! 在绘制完成之后,还需要调用 closePath() 方法!!!

弧线

arcTo() 可以实现画弧线。当然 arc() 也可以,只不过使用 arc() 情况下不能使用 closePath(),会带来额外的问题。

arcTo(cx, cy, x2, y2, radius)
  • cx: 两切线交点的横坐标
  • cy: 两切线交点的纵坐标
  • x2: 结束点的横坐标
  • y2: 结束点的纵坐标
  • radius: 半径

其中,(cx, cy) 也叫控制点,(x2, y2) 也叫结束点。开始点则由 moveTo() 或者 lineTo() 提供。
canvas

js
    cxt.moveTo(40, 40)
    cxt.arcTo(120, 40, 120, 120, 80)

    cxt.stroke()

canvas

字体

样式

js
cxt.font = 'font-style font-variant font-weight font-size/line-height font-family'

描边 strokeText()

js
strokeText(text, x, y, maxWidth)
  • text: 字符串,要绘制的内容
  • x: 横坐标,文本左边要对齐的坐标(默认左对齐)
  • y: 纵坐标,文本底边要对齐的坐标
  • maxWidth: 可选参数,表示文本渲染的最大宽度(px),如果文本超出 maxWidth 设置的值,文本会被压缩
    canvas

可以通过 strokeStyle 设置字体颜色

填充 fillText

js
fillText(text, x, y, maxWidth)
  • text: 字符串,要绘制的内容
  • x: 横坐标,文本左边要对齐的坐标(默认左对齐)
  • y: 纵坐标,文本底边要对齐的坐标
  • maxWidth: 可选参数,表示文本渲染的最大宽度(px),如果文本超出 maxWidth 设置的值,文本会被压缩
    canvas

可以通过 fillStyle 设置字体颜色

获取文本长度 measureText()

measureText().width 方法可以获取文本的长度,单位是 px 。

js
    let text = '雷猴'
    cxt.font = 'bold 40px Arial'
    cxt.fillText(text, 40, 80)

    console.log(cxt.measureText(text).width) // 80

字体对齐

水平对齐方式 textAlign

使用 textAlign 属性可以设置文字的水平对齐方式,一共有5个值可选

  • start: 默认。在指定位置的横坐标开始。
  • end: 在指定坐标的横坐标结束。
  • left: 左对齐。
  • right: 右对齐。
  • center: 居中对齐。

垂直对齐方式 textBaseline

使用 textBaseline 属性可以设置文字的垂直对齐方式。

  • alphabetic: 默认。文本基线是普通的字母基线。
  • top: 文本基线是 em 方框的顶端。
  • bottom: 文本基线是 em 方框的底端。
  • middle: 文本基线是 em 方框的正中。
  • hanging: 文本基线是悬挂基线

图片

使用 drawImage() 可以渲染图片

js
drawImage(image, dx, dy, dw, dh)
  • image 图片源
  • dx x 轴位置
  • dy y 轴位置
  • dw 图片宽度
  • dh 图片高度

渲染图片的方式有2中,一种是在JS里加载图片再渲染,另一种是把DOM里的图片拿到 canvas 里渲染。

JS版

在 JS 里加载图片并渲染,有以下几个步骤:

  1. 创建 Image 对象
  2. 引入图片
  3. 等待图片加载完成
  4. 使用 drawImage() 方法渲染图片
js
    // 1 创建 Image 对象
    const image = new Image()

    // 2 引入图片
    image.src = './images/dog.jpg'

    // 3 等待图片加载完成
    image.onload = () => {
        // 4 使用 drawImage() 方法渲染图片
        cxt.drawImage(image, 30, 30)
    }

DOM版

html
    <img src="./images/dog.jpg" id="dogImg"/>
js
    const image = document.getElementById('dogImg')

    cxt.drawImage(image, 70, 70)

截取图片

截图图片同样使用drawImage() 方法,只不过传入的参数数量比之前都多,而且顺序也有点不一样了。

js
drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)
  • image: 图片源
  • sx: 开始截取的横坐标
  • sy: 开始截取的纵坐标
  • sw: 截取的宽度
  • sh: 截取的高度
  • dx: 图片左上角的横坐标位置
  • dy: 图片左上角的纵坐标位置
  • dw: 图片宽度
  • dh: 图片高度