跳到主要内容

谈谈canvas

  • canvas相关场景
  • canvas相关优化

Canvas 和 SVG 有什么区别​

  • <canvas> 标签本身是 DOM 元素,但是通过canvas绘制出来的内容不是DOM元素,而是像素
对比CanvasSVG / DOM
外层本身<canvas>是 DOM<svg> 是 DOM
内部图形不是 DOM,是像素是 DOM 节点
事件绑定绑定在 canvas 上,自己做命中检测每个图形可单独绑定
样式控制通过 JS 重绘可用 CSS/属性控制
适合大量图形、游戏、视频帧、画板图标、流程图、可交互图形
缩放位图,可能模糊矢量,缩放清晰

Canvas 为什么会模糊?怎么解决?​

  • Canvas 会模糊,主要是因为 Canvas 绘制结果是位图。如果 canvas 的实际像素尺寸和 CSS 显示尺寸不一致,浏览器会缩放这张位图,导致模糊

  • 解决方案:

    • 通过设备像素比DPR来适配 (devicePixelRatio)

    • 通过 window.devicePixelRatio 获取屏幕像素比,将 Canvas 的宽度和高度放大相应倍数,并使用 scale() 方法适配

核心代码

js
function resizeCanvas(canvas, width, height) {
const dpr = window.devicePixelRatio || 1
const ctx = canvas.getContext('2d')

canvas.style.width = width + 'px'
canvas.style.height = height + 'px'

canvas.width = Math.round(width * dpr)
canvas.height = Math.round(height * dpr)

ctx.scale(dpr, dpr)

return ctx
}

Canvas 如何做动画​

  • 标准做法是用 requestAnimationFrame,不要用 setInterval。MDN 的 Canvas 优化建议里也明确提到:动画应使用 Window.requestAnimationFrame() 而不是 setInterval()。
  • requestAnimationFrame 会和浏览器刷新频率同步,页面不可见时通常会降频或暂停,减少无效渲染

Canvas 怎么绑定点击事件​

  • Canvas 内部图形不是 DOM 元素,不能直接给某个圆或矩形绑定事件。通常做法是
    • 给 canvas 本身绑定事件
    • 获取鼠标相对 canvas 的坐标。

Canvas 如何导出图片​

  • 通过toDataURL方法
  • 如果 canvas 绘制了跨域图片,并且图片没有正确设置 CORS,canvas 会被污染,调用 toDataURL / getImageData 会报安全错误
  • 图片的 crossorigin 属性需要结合合适的 CORS 响应头,才能让跨源图片像同源图片一样安全地用于 Canvas

Canvas 如何绘制跨域图片并导出?

  • 只是告诉浏览器,希望用 CORS 模式加载这张图片
    • anonymous(匿名):告知浏览器以匿名方式获取资源
js
const img = new Image()
img.crossOrigin = 'anonymous'
img.src = url
  • 服务器也必须允许跨域,比如图片服务器需要返回
js
Access-Control-Allow-Origin: *

Canvas在音视频业务应用​

  • canvas来绘制时间轴
  • canvas处理视频画面帧
  • canvas实现截图、封面生成

绘制时间轴​

在视频剪辑、音频剪辑、录课回放、直播回放、字幕编辑器中,时间轴通常会用 Canvas 来绘制

  • Canvas 适合时间轴的原因是:时间轴通常包含大量刻度、片段、辅助线,如果用 DOM 渲染大量节点,性能和布局成本会比较高;Canvas 可以把这些内容一次性绘制到位,来提高性能

视频截图 / 封面生成​

  • Canvas 可以把视频当前帧截出来,常用于生成视频封面。

核心方法:drawImage 、toDataURL

Canvas相关优化​

Canvas 优化主要从减少绘制、减少计算、减少状态切换、减少主线程压力几个方向入手。动画使用 requestAnimationFrame;静态内容用离屏 canvas 缓存;复杂场景用多 canvas 分层;只重绘变化区域;大量图形按样式分组批量绘制;避免频繁 getImageData、putImageData、toDataURL、shadowBlur 和文本绘制;图片提前加载和预缩放

  • 减少绘制次数
    • 不要每次状态变化都立刻画,应该合并到一帧中
  • 只重绘变化区域
    • 尽量只渲染屏幕差异,而不是每次都渲染完整新状态
  • 分层 Canvas
    • 把一个复杂画布拆成多个 canvas 叠在一起
    • 好处:某一层变化时,只重绘那一层。