之前需要写一个在地图上进行图形绘制的组件,考虑到需求复杂度问题,不想导入 fabric 等 canvas 库,想直接使用 canvas 原生 api 进行编写,结果发现原生 api 比我想像中的要不易用一些,最后写下来感觉像是写了一个小型库,特意对比了一下 fabric 在这一块实现的源码,竟然有些相似,哈哈!这里做下简单的笔记,以及一些注意事项
context core api
CanvasRenderingContext2D 原生 API
- 描边类绘制
- strokeStyle
- stroke(path2D?):描边当前子路径或指定 path2D
- strokeXXX
- 填充类绘制
- fillStyle
- fill(path2D?):填充当前子路径或指定 path2D
- fillXXX
- 路径
- beginPath:清空子路径列表开始一个新的路径
- moveTo
- lineTo/rect/arc/arcTo/……
- closePath:尝试闭合图形,如果已经封闭或者只有一个点,则不作任何操作
- 线型
- lineWidth
- lineCap
- lineJoin
- setLineDash
- 文本样式
- font
- textAlign
- textBaseline
- direction
- 阴影
- shadowBlur
- shadowColor
- shadowOffsetX
- shadowOffsetY
- 合成
- globalAlpha
- globalCompositeOperation
- 像素控制
- createImageData
- getImageData
- putImageData
- 状态:线的样式、填充样式、阴影样式、文本样式
- save
- restore
- 变换
- rotate
- scale
- translate
- transform 叠加变换
- setTransform 设置变换矩阵
- 特别注意:多次调用 rotate、scale、translate 是对当前状态的叠加效果
- 其他
- clearRect
- clip:用于将当前正在构建的路径转换为当前的裁剪路径。默认情况下,canvas 有一个与它自身一样大的裁切路径(也就是没有裁切效果)。
- toDataURL
- toBlob
画布裁剪
画布清除
使用 clearRect 清空画布的场景
- 没有使用 transform 的 context
- 如果使用了 transform 的 context,则需要跟踪实际边界
或者使用如下方式
// Store the current transformation matrix
context.save();
// Use the identity matrix while clearing the canvas
context.setTransform(1, 0, 0, 1, 0, 0);
context.clearRect(0, 0, canvas.width, canvas.height);
// Restore the transform
context.restore();
重设 canvas 的 width、height 会重置 canvas 的状态,比如 transformations、lineWidth、strokeStyle 等。注意:不要使用这种方式去清空画布,因为相比 clearRect 而言,非常低效
性能优化
mdm 上有对 canvas 常见性能优化进行整理,这里也简单罗列一下
- 在离屏 canvas 上预渲染相似的图形或重复的对象
- 避免浮点数的坐标点,用整数取而代之
- 不要在用 drawImage 时缩放图像
- 使用多层画布去画一个复杂的场景:动静分离
- 用 CSS 设置大的背景图
- 用 CSS transforms 特性缩放画布:CSS transforms 使用 GPU,因此速度更快
- 关闭透明度
- 将画布的函数调用集合到一起(例如,画一条折线,而不要画多条分开的直线)
- 渲染画布中的不同点,而非整个新状态
- 尽可能避免 shadowBlur 特性
- 尽可能避免 text rendering
- 尝试不同的方法来清除画布(clearRect() vs. fillRect() vs. 调整canvas大小)
- 有动画,请使用 window.requestAnimationFrame() 而非 window.setInterval()
注意事项
其他值得注意的点
- 每次调用 getContext('2d') 获取 context,会是一个全新的 context
- 当 canvas 容器 display 为 none 时,render 通常会导致异常