Canvas 极速入门

911 阅读6分钟

简介

Canvas 是 HTML5 新增的一个标签 <canvas> </canvas>

仅该标签,实际上只是一个矩形的画布,要想赋予该画布以内容,就需要用到 JavaScript 了。

扫一眼浏览器兼容性,已经支持的很好了……

Canvas 浏览器兼容性

图片来源:Can I use Canvas

学习之前,先了解 Canvas 能做什么?

  • 简单图画/动画
  • 鼎鼎大名的可视化库 ECharts
  • 图片生成:截图,简单 PS,含滤镜、抠图、缩放、旋转、位移、形变等
  • 音频、视频加工,录制和合成
  • ...

能力图:

开发工具

IDE:VS Code 不用多说。

智能提示:原生开发中,只需要在相应代码前添加 /** @type {HTMLCanvasElement} */ 即可。

/** @type {HTMLCanvasElement} */
// 这里是具体的 canvas 代码

使用封装好的 Canvas JavaScript 库:则可以省略上面一步。

快速入门

1. 标签和 API 是否支持

<!-- canvas 标签不兼容则默认转成 div。 -->
<!-- 避免使用 css 定宽高,这将与 canvas 的初始化宽高冲突,出现拉伸等形变。 -->
<canvas id="canvasElem" width=600 height=600>
  你的浏览器不支持 canvas,请先升级浏览器。
</canvas>
let canvas = document.querySelector('#canvasElem')
if (canvas.getContext) {
	// Context 对象就是 JavaScript 操作 Canvas 的接口,入参可选:2d/webgl
	// 绘制 2d 上下文,记住这个 ctx 变量
	let ctx = canvas.getContext('2d')
} else {
	alert('你的浏览器不支持 canvas,请先升级浏览器。')
}

2.坐标系

Canvas 的坐标系原点 (0,0) 是从画布的左上角开始的,需要注意的是:

  • 文字内容 在不设置 对齐方式本文基线 的情况下,默认以左上角为参考,如图坐标点 (200, 200)
  • 圆的坐标系,角度以 顺时针 递增。

3.极速入门

先了解基础 API:

  • ctx.moveTo(x, y):移动画笔到 (x, y) 点后开始绘制。

  • ctx.beginPath():新建一条路径,绘制命令将转移到新建的路径上,通常在改变起始点和路径样式时使用。

  • ctx.lineTo(x, y):从 moveTo 的点到 lineTo 的点的线段。

  • ctx.stroke():给路径描边。

  • ctx.fill():将封闭的区域进行填充,默认黑色。

  • ctx.clearRect(x, y, w, h):清除以 (x, y) 为起点,宽高为 w,h 的区域

简单画个黑色矩形:

/** @type {HTMLCanvasElement} */
let canvas = document.getElementById('canvasElem')
canvas.style.border = '1px solid blue'
let ctx = canvas.getContext('2d')
ctx.beginPath() // 这里可以省略,因为只有一条路径
ctx.moveTo(50, 50) // 从 (50, 50) 开始
ctx.lineTo(50, 150)
ctx.lineTo(150, 150)
ctx.lineTo(150, 50)
ctx.closePath() // 自动闭合
ctx.stroke() // 描边
ctx.fill() // 闭合区域着色

// 或者你可以一步搞定
let ctx = canvas.getContext('2d')
ctx.fillRect(50, 50, 100, 100)
// 此时,你已经可以右键保存图片了...

通过 strokeStyle、fillStyle、createLinearGradient 等 API 简单加点样式(css 能做到的,Canvas 一样也可以):

其中 createLinearGradient 需要通过 JavaScript 计算得出渐变效果,假如你想在移动端有个良好的性能体验,建议 不要使用!类似的还有 createRadialGradient(耗电、计算渲染慢,gif 也是一样的毛病)。

不使用?那我就要这样的效果呢?那就换个思路:

ctx.drawImage(image, x, y, w, h):image 为图片对象,放置至画布中以 (x, y) 为起点,宽高为 w,h 的区域。

我们完全可以事先准备若干张复杂的图片,接着绘制到 Canvas 画布上,然后二次加工...

/** @type {HTMLCanvasElement} */
let canvas = document.getElementById('canvasElem')
let ctx = canvas.getContext('2d')
let img = new Image()
img.crossOrigin = 'anonymous' // 不需要跨域凭证,如需兼容 IE10,使用 ajax 解决
img.onload = function() {
    // 假若一张大图绘在一张小的画布上,就实现了压缩!或使用 HTMLCanvasElement.toBlob()
    ctx.drawImage(img, 0, 0, 120, 120)
    // 你还可以将画图拉伸、形变,或直接转成 base64 赋给一个空 src 的 <img>
    // toDataURL 跨域配置 CORS
    let dataURL = canvas.toDataURL('image/jpeg', 1)
    console.log(dataURL)
}
img.src = './rectangle.jpeg'

简单加个水印,可以选择图片或文字 ctx.fillText('ctx.fillText', x, y)

// drawImage 也能够实现 img 的裁剪
ctx.drawImage(img, 0, 0, 120, 28, 8, 190, 120, 28)

// 或者这样,注意文本的位置是以左上角 (8, 190) 为准
// 注意点:Canvas 对于长内容文本自动换行的支持很弱
ctx.font = "16px Arial"
ctx.fillText('洛羽Keith 掘金', 8, 190)

现在,我想让这个矩形跑起来,需要用到 requestanimationframe

/** @type {HTMLCanvasElement} */
let canvas = document.getElementById('canvasElem')
canvas.style.border = '1px solid #ccc'
let ctx = canvas.getContext('2d')
let img = new Image()
img.onload = function() {
  let offsetX = 0, cw = ctx.canvas.width, ch = ctx.canvas.height,
      iw = img.width, ih = img.height
  function animate() {
    ctx.clearRect(0, 30, cw, ch - 60) // 清空画布指定区域
    ctx.drawImage(img, 0, 0, iw, ih, offsetX * 5 - 200, 0, iw, ih)
    offsetX++
    offsetX %= 70
    window.requestAnimationFrame(animate)
  }
  animate()
}
img.src = './rectangle.png'

再强化下,模拟一个 九宫格抽奖

当然,假如你想写出更加酷炫的 2D 动效,还需要一些动画算法的学习,比如 缓动算法...

好了,快速入门案例点到为止。

4.优化与权衡

🌰 动画循环:能用 requestAnimationFrame,就不要用定时器,记得 polyfill。

🌰 离屏缓冲:你可以在一个看不见的 画布1 中绘制复杂的内容(甚至可以在 Web Worker 中),然后一次性 drawImage 在另一个可见的 画布2 上,以应对一些特定的场景(用后别忘了销毁)。

🌰 合理利用缓存和裁剪ctx.save() 保存当前环境的状态到缓存中,包括路径、属性、坐标起始点。ctx.restore() 返回之前保存过的路径状态和属性。ctx.clip() 剪辑区域可供擦除和复制。

🌰 使用 CSS 的地方:将复杂背景直接放在 div 下,使用 background-image 更高效,另外充分利用 css3 的 GPU 性能,避免使用 Canvas 提供的阴影、渐变、转换等 API。

🌰 视频网站的弹幕性能研究

我们知道现在的主流视频网站都有 弹幕 这一功能,弹幕可以用于交流和沟通。实现弹幕的方法我所知的有两种,一种是 dom+css 操作,一种是 Canvas 动画绘制,单纯从 横屏滚动 这一功能来说,可能 Canvas 的性能更好,但是为什么主流视频网站都选择使用 dom 呢?原因有以下几点:

  1. 使用 Canvas 绘制耗电更快,会造成电池发热,这也是 Canvas 开发 游戏不火的原因。
  2. 现在的弹幕需要满足用户的一些需求,比如显示头像、显示广告、对好的弹幕点赞、对坏的弹幕投诉、过滤重复的弹幕、总能看到自己发送的弹幕、高级弹幕等等,Canvas 的事件机制显然没有 dom 更强大。
  3. 弹幕销毁、多条弹道、帧率的考虑

🌰 第三方库

第三方库的好处是简单的面向对象编程,完善的体系,已经封装了动画、事件系统等功能模块。介绍以下:

小巧易用的 Konva

html2canvas

国内的游戏引擎 Egret

强大的 three.js,涉及 webgl,超出本文内容。

所以,快去使用第三方库!

5.其他 API

  • ctx.measureText(text).width:本文宽度计算。
  • ctx.isPointInPath(x,y):判断 (x, y) 坐标点是否在当前的路径中。
  • ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y):绘制贝塞尔曲线。
  • 色块填充规则,选择性了解。

最后

文章产出不易,还望各位小伙伴们支持一波!如有误,请不吝指正。

参考资料

Canvas API 文档

Canvas 性能优化

Canvas 应用大全