Canvas本质是位图帧缓冲区,仅提供可编程像素缓冲区,支持图像处理、高频动画、UI分层等高性能场景,但需自行实现交互与状态管理。

Canvas 标签不是“只能画图”,但它确实**只做一件事:提供一块可编程的像素缓冲区**。它不渲染 DOM、不管理事件绑定、不保存图形状态——所有“功能”都来自你用 JavaScript 对这块缓冲区的读写操作。
canvas 能做的远不止 fillRect 和 strokeLine
很多人一看到 <canvas> 就默认它是“画矢量图的替代 SVG 的工具”,这是最大误解。Canvas 的本质是「位图帧缓冲」,这意味着:
- 你可以用
getImageData和putImageData直接读写每个像素的 RGBA 值,做灰度化、边缘检测、色彩抖动等图像处理 - 配合
requestAnimationFrame+clearRect+ 重绘逻辑,能实现每秒 60 帧的粒子系统或地图缩放动画 - 用
createPattern铺贴图、setTransform做局部坐标系变换,可模拟 UI 组件层级(比如带滚动的表格单元格) - 把多个
<canvas>叠在一起(CSSposition: absolute),分别负责背景、主体、UI 层,避免整屏重绘
典型高性能场景:电子表格渲染(只重绘可视区域)、实时视频滤镜(video → drawImage → 像素处理 → 显示)、2D 游戏主循环(无 DOM 干扰,CPU 占用可控)。
为什么不用 DOM 或 SVG 做这些事
当你要每帧更新上千个元素时,DOM 操作成本会指数级上升:
立即学习“前端免费学习笔记(深入)”;
文章转信息图。将文章/笔记转化为手机可读的 HTML 信息图,自动匹配视觉风格。触发场景:文章转图、笔记转图、信息图、转小红书图、做张图、可视化这篇文章、文生图。
-
div每个都是独立布局+样式+事件对象,1000 个就是 1000 个 DOM 节点,innerHTML或style.left/top触发重排重绘 - SVG 是 DOM 子集,每个
<rect>也是真实节点,getBBox()或addEventListener开销大,缩放时还要重新计算 viewBox - Canvas 没有节点概念,只有内存中一块二维数组——移动一个“角色”,只需改两三个变量,然后
fillRect(x, y, w, h)一次调用
代价是:你得自己实现点击检测(比如记录矩形坐标并比对 mouseX/mouseY)、自己管理状态(比如“这个按钮是否 hover”)、自己处理缩放适配(devicePixelRatio 补偿)。
drawImage 加载图片失败的静默陷阱
新手常写的代码:img.src = 'xxx.png'; ctx.drawImage(img, 0, 0); —— 这几乎必然失败,因为 drawImage 执行时图片大概率还没加载完。
- 现象:canvas 一片空白,控制台无报错,
ctx.drawImage静默跳过 - 正确做法必须监听
img.onload,或者用await createImageBitmap(img)(兼容性需查 MDN) - 跨域图片必须加
img.crossOrigin = 'anonymous',否则调用getImageData会触发安全错误 - 如果图像是从
fetch+arrayBuffer构建的,要用createImageBitmap而非直接 new Image(),否则无法保证解码完成
高清屏下 canvas 模糊的根本原因
不是 CSS 缩放问题,而是「设备像素比」没对齐。
- HTML
width="400" height="300"定义的是 CSS 像素尺寸,但 retina 屏上 1 个 CSS 像素对应 2×2 物理像素 - 结果:你往 400×300 的缓冲区里画了 400×300 个像素,但被拉伸到 800×600 物理像素显示,自然模糊
- 修复方式:用
window.devicePixelRatio动态设置 canvas 的width/height属性,并用 CSS 控制显示尺寸const dpr = window.devicePixelRatio || 1;<br>canvas.width = 400 * dpr;<br>canvas.height = 300 * dpr;<br>canvas.style.width = '400px';<br>canvas.style.height = '300px';<br>ctx.scale(dpr, dpr);
这个缩放步骤不能省——ctx.scale 让所有后续绘图坐标自动适配,否则你要手动把每个 x/y 都乘 dpr。


















