简介
Canvas 是 HTML5 新增的一个标签 <canvas> </canvas>
仅该标签,实际上只是一个矩形的画布,要想赋予该画布以内容,就需要用到 JavaScript 了。
扫一眼浏览器兼容性,已经支持的很好了……
图片来源: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 呢?原因有以下几点:
- 使用 Canvas 绘制耗电更快,会造成电池发热,这也是 Canvas 开发 大 游戏不火的原因。
- 现在的弹幕需要满足用户的一些需求,比如显示头像、显示广告、对好的弹幕点赞、对坏的弹幕投诉、过滤重复的弹幕、总能看到自己发送的弹幕、高级弹幕等等,Canvas 的事件机制显然没有 dom 更强大。
- 弹幕销毁、多条弹道、帧率的考虑
🌰 第三方库:
第三方库的好处是简单的面向对象编程,完善的体系,已经封装了动画、事件系统等功能模块。介绍以下:
所以,快去使用第三方库!
5.其他 API
ctx.measureText(text).width
:本文宽度计算。ctx.isPointInPath(x,y)
:判断 (x, y) 坐标点是否在当前的路径中。ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
:绘制贝塞尔曲线。- 色块填充规则,选择性了解。
最后
文章产出不易,还望各位小伙伴们支持一波!如有误,请不吝指正。