canvas教程
2026/9/15 7:28:14 网站建设 项目流程

你如果是以前端面试 + 公司实际开发为目标学 Canvas,我建议不要只学“画矩形、画圆”,而是按:

基础 API → 图片处理 → 交互 → 动画 → 性能 → 实际业务场景 → 工程化 → 面试高频问题

这条路线学。

Canvas 2D 本质上就是拿到CanvasRenderingContext2D,然后通过它绘制形状、文字、图片等内容。(MDN Web Docs)


一、Canvas 到底是什么

最简单:

<canvas id="canvas" width="500" height="300"></canvas>

JS:

const canvas = document.getElementById('canvas') const ctx = canvas.getContext('2d') ctx.fillStyle = 'red' ctx.fillRect(100, 50, 200, 100)

可以把 Canvas 理解成:

Canvas ↓ 一块“位图画布” ↓ JS 操作 ctx ↓ 往里面画东西

和普通 DOM 最大区别:

DOM: <div> <span>hello</span> </div> 浏览器维护: 元素 → 属性 → 样式 → 布局 → 渲染

Canvas:

<canvas> ↓ 像素 ↓ 你自己画

Canvas 画完以后,浏览器并不知道你画了一个“按钮”还是“圆形”。

所以:

Canvas 更像一个“画板”,而不是 DOM 元素树。


二、Canvas 基础

1. 创建 Canvas

<canvas id="canvas"></canvas>
const canvas = document.querySelector('#canvas') const ctx = canvas.getContext('2d')

常见 context:

canvas.getContext('2d') canvas.getContext('webgl')

公司前端业务中,2D Canvas 最常见


三、Canvas 宽高非常重要

这是一个特别容易踩坑的地方。

推荐:

<canvas width="800" height="600"></canvas>

而不是:

canvas { width: 800px; height: 600px; }

两者不是一回事。


Canvas 有两套尺寸

Canvas 实际像素尺寸 ↓ width / height Canvas CSS 显示尺寸 ↓ style width / height

例如:

<canvas width="800" height="600" style="width:400px;height:300px"> </canvas>

实际绘制:

800 × 600

显示:

400 × 300

相当于浏览器进行了缩放。


四、画矩形

最常用的几个 API。

fillRect

ctx.fillStyle = 'red' ctx.fillRect( 100, 50, 200, 100 )

参数:

x y width height

strokeRect

只画边框:

ctx.strokeStyle = 'blue' ctx.lineWidth = 5 ctx.strokeRect(100, 50, 200, 100)

clearRect

清除区域:

ctx.clearRect( 0, 0, canvas.width, canvas.height )

动画里面非常常见。


五、画路径

Canvas 真正强大的地方是 Path。

ctx.beginPath() ctx.moveTo(100, 100) ctx.lineTo(200, 100) ctx.lineTo(150, 200) ctx.closePath() ctx.stroke()

画的是:

100,100 -------- 200,100 \ / \ / \ / 150,200

六、beginPath 为什么重要

例如:

ctx.beginPath() ctx.moveTo(100, 100) ctx.lineTo(200, 100) ctx.stroke()

再画一条:

ctx.moveTo(100, 200) ctx.lineTo(200, 200) ctx.stroke()
  1. 第一次stroke():绘制当前路径(线 1),路径还留在上下文中,不清除
  2. moveTo/lineTo:继续往同一个路径对象追加,现在路径 = 线 1 + 线 2
  3. 第二次stroke():把全部路径再次描边 →线 1 被重复绘制 2 次

后果:

  1. 如果线条有透明度,第一条线会变深;
  2. 如果换了strokeStyle颜色,第一条线也会变成新颜色!(最经典坑)

推荐始终明确:

ctx.beginPath() ... ctx.stroke() ctx.beginPath() ... ctx.stroke()

beginPath()的作用:新建一条独立路径,切断和之前旧路径的关联

stroke()/fill()只是把「当前所有累积的路径」画出来,不会清空路径

否则路径可能不断累积。


七、画线

ctx.beginPath() ctx.moveTo(50, 50) ctx.lineTo(200, 100) ctx.lineTo(300, 50) ctx.stroke()

常用:

ctx.lineWidth = 2 ctx.strokeStyle = '#333' ctx.lineCap = 'round' ctx.lineJoin = 'round'
属性控制什么常用值
lineCap线段两端butt / round / square
lineJoin折线拐角miter / round / bevel
lineWidth线宽数字

八、画圆

ctx.beginPath() ctx.arc( 200, 150, 50, 0, Math.PI * 2 ) ctx.fill()

参数:

x y radius startAngle //起始弧度 endAngle //结束弧度

完整:

ctx.beginPath() //0度画到2Π ctx.arc( 200, 150, 50, 0, Math.PI * 2 ) ctx.fillStyle = 'red' ctx.fill()

九、Canvas 颜色

fillStyle

填充颜色:

ctx.fillStyle = '#ff0000'

strokeStyle

边框颜色:

ctx.strokeStyle = '#000'

透明度

ctx.globalAlpha = 0.5

或者:

ctx.fillStyle = 'rgba(255,0,0,0.5)'

十、渐变

实际项目里也经常用。

线性渐变

//起点(0,0) → 终点(300,0) const gradient = ctx.createLinearGradient( 0, 0, 300, 0 ) gradient.addColorStop(0, 'red') gradient.addColorStop(1, 'blue') // 把填充样式设置成这个渐变 ctx.fillStyle = gradient // 画矩形,使用上面的渐变填充,在渐变范围内的形状都是渐变色的 ctx.fillRect(0, 0, 300, 100)

径向渐变

const gradient = ctx.createRadialGradient( 150, 150, 20, 150, 150, 100 ) gradient.addColorStop(0, 'white') gradient.addColorStop(1, 'blue') ctx.fillStyle = gradient ctx.fillRect(0, 0, 300, 300)

十一、Canvas 文字

ctx.font = '30px Arial' ctx.fillStyle = 'black' ctx.fillText( 'Hello Canvas', 100, 100 )

strokeText

ctx.strokeText( 'Hello', 100, 100 )

文字对齐

ctx.textAlign = 'center' ctx.textBaseline = 'middle'

例如:

ctx.textAlign = 'center' // 水平对齐:文字中心点对准 x坐标 ctx.textBaseline = 'middle' // 垂直对齐:文字中心点对准 y坐标 ctx.fillText( 'Hello', canvas.width / 2, canvas.height / 2 )

这个在:

  • 验证码

  • 海报

  • 图片水印

  • 图表

里面都非常常见。


十二、保存和恢复状态

这是 Canvas 很重要的 API。

ctx.save() ctx.fillStyle = 'red' ctx.translate(100, 100) ctx.rotate(Math.PI / 4) ctx.fillRect(0, 0, 100, 100) ctx.restore()

可以理解:

save() ↓ 保存当前绘图状态 修改各种属性 ↓ 绘制 restore() ↓ 恢复之前状态

save()把当前画布所有绘图状态压栈保存;restore()弹出,恢复到上次 save 时的状态。

⚠️ 重点:保存的是【绘图状态】,不是画布上已经画出来的像素!已经画在画布的图形,restore 不会擦掉。就能从之前的状态继续画

实际开发非常推荐:

ctx.save() // 修改状态 // 绘制 ctx.restore()

避免污染后面的绘制。


十三、坐标变换

Canvas 的高级开发经常会用到。

translate

移动坐标系:

ctx.translate(100, 50)

之后:

ctx.fillRect(0, 0, 100, 100)

实际上相当于:

从 100,50 开始画

rotate

旋转:

ctx.rotate(Math.PI / 4)

注意:

Math.PI

等于:

180°

所以:

Math.PI / 2

就是:

90°

scale

ctx.scale(2, 2)

相当于放大 2 倍。


十四、图片绘制——公司最重要的一部分

Canvas 很多实际项目不是“画图形”,而是:

处理图片。

核心 API:

ctx.drawImage()

它支持多种图片源,包括HTMLImageElementHTMLVideoElementHTMLCanvasElementImageBitmap等。(MDN Web Docs)


最简单

const img = new Image() img.onload = () => { ctx.drawImage(img, 0, 0) } img.src = '/test.jpg'

注意一定要等:

img.onload

否则图片可能还没加载完成。


十五、图片缩放

ctx.drawImage( img, 0, 0, 300, 200 )

把图片画成:

300 × 200

十六、图片裁剪

drawImage最强大的地方之一。

ctx.drawImage( img, sx, sy, sw, sh, dx, dy, dw, dh )

前 4 个:源裁切(从原图挖一块)

  1. sx:源图片x 起点(原图坐标系)
  2. sy:源图片y 起点
  3. sw:源图片裁切宽度(要挖多宽)
  4. sh:源图片裁切高度(要挖多高)

后 4 个:绘制到画布(把挖出来的块画到 canvas)

5.dx:画布上x 起点

6.dy:画布上y 起点

7.dw:画布上绘制的宽度(拉伸 / 压缩)

8.dh:画布上绘制的高度(拉伸 / 压缩)

意思:

原图: ┌──────────────────┐ │ │ │ ┌──────┐ │ │ │裁剪区│ │ │ └──────┘ │ │ │ └──────────────────┘ ↓ Canvas ┌──────────────┐ │ │ │ 裁剪内容 │ │ │ └──────────────┘

例如:

ctx.drawImage( img, 100, 100, 300, 300, 0, 0, 300, 300 )

十七、公司实际场景 ①:图片压缩

这个非常重要。

你之前关注过的:

用户上传原图 → 前端压缩 → WebP → OSS

Canvas 就是核心技术之一。

流程:

用户选择图片 ↓ File ↓ Image ↓ Canvas ↓ 限制最大宽高 ↓ drawImage ↓ toBlob ↓ 压缩后的 Blob/File ↓ OSS ↓ 拿到 URL

一个实际的图片压缩函数

function compressImage(file, maxWidth = 2048, quality = 0.8) { return new Promise((resolve, reject) => { const img = new Image() const url = URL.createObjectURL(file) img.onload = () => { URL.revokeObjectURL(url) let { width, height } = img let scale = 1 // 调整图片缩放,限制图片宽高,像素太高的图片会被压像素,会有个像素封顶值 if (width > maxWidth) scale = Math.min(scale, maxWidth / width) if (height > maxHeight) scale = Math.min(scale, maxHeight / height) width = width * scale height = height * scale const canvas = document.createElement('canvas') canvas.width = width canvas.height = height const ctx = canvas.getContext('2d') ctx.drawImage( img, 0, 0, width, height ) canvas.toBlob( blob => { if (!blob) { reject(new Error('压缩失败')) return } resolve(blob) }, 'image/webp', quality ) } img.onerror = reject img.src = url }) }

最大像素:2048*2048 ≈ 400万像素,内存固定上限≈16MB

使用:

const blob = await compressImage(file) const compressedFile = new File( [blob], 'compressed.webp', { type: 'image/webp' } )

十八、toBlob vs toDataURL

这是面试高频。

toDataURL

const url = canvas.toDataURL( 'image/jpeg', 0.8 )

得到:

data:image/jpeg;base64,...

缺点:

Canvas ↓ Base64 ↓ 字符串 ↓ 内存占用较大

所以实际上传文件一般不推荐。


toBlob

canvas.toBlob(blob => { console.log(blob) }, 'image/jpeg', 0.8)

得到:

Blob

然后:

const file = new File( [blob], 'image.jpg', { type: 'image/jpeg' } )

实际开发:

上传图片优先toBlob

MDN 也明确提供了toBlob()用于创建 Canvas 内容对应的 Blob。(GitHub)


十九、公司实际场景 ②:图片裁剪

例如头像上传。

用户选择图片 ↓ Canvas 显示图片 ↓ 用户拖动裁剪框 ↓ 计算裁剪区域 ↓ drawImage ↓ 生成 Blob ↓ 上传

核心:

ctx.drawImage( img, cropX, cropY, cropWidth, cropHeight, 0, 0, outputWidth, outputHeight )

这就是:

Canvas 图片裁剪器

很多头像裁剪、证件照裁剪、商品图裁剪,本质都是这个。


二十、公司实际场景 ③:水印

例如:

用户上传图片 ↓ Canvas ↓ drawImage ↓ fillText("xxx公司") ↓ toBlob ↓ 上传

代码:

ctx.drawImage( img, 0, 0, width, height ) ctx.font = '30px Arial' ctx.fillStyle = 'rgba(255,255,255,0.5)' ctx.fillText( 'Company', 30, height - 30 )

二十一、公司实际场景 ④:图片拼接

例如把:

图片 A 图片 B 图片 C

拼成:

┌─────────┐ │ 图片 A │ ├─────────┤ │ 图片 B │ ├─────────┤ │ 图片 C │ └─────────┘

Canvas:

ctx.drawImage(img1, 0, 0) ctx.drawImage(img2, 0, height1) ctx.drawImage(img3, 0, height1 + height2)

最后:

canvas.toBlob(...)

可以生成一张新图片。


二十二、公司实际场景 ⑤:截图

这个很常见,但要注意:

Canvas 本身不能直接把任意 DOM “截图”。

通常是:

DOM ↓ html2canvas 等库 ↓ Canvas ↓ Blob / Image

或者:

浏览器 Screenshot API

所以面试不要说:

Canvas 可以直接截网页。

更准确:

Canvas 可以作为截图结果的绘制载体,DOM 截图通常需要额外的 DOM → Canvas 方案。


二十三、公司实际场景 ⑥:验证码

例如:

┌─────────────┐ │ A 7 K 2 │ │ \ /\/\/ │ │ 噪声 │ └─────────────┘

Canvas:

ctx.font = '30px Arial' ctx.fillText('A7K2', 20, 40)

然后:

ctx.beginPath() ctx.moveTo(...) ctx.lineTo(...) ctx.stroke()

加入:

  • 随机颜色

  • 随机字体

  • 随机旋转

  • 干扰线

  • 噪点


二十四、公司实际场景 ⑦:签名板

这个非常典型。

比如:

用户在手机上签名 ┌───────────────────────┐ │ │ │ 手写签名 │ │ │ └───────────────────────┘

核心:

canvas.addEventListener( 'pointerdown', e => { ctx.beginPath() ctx.moveTo(e.offsetX, e.offsetY) } ) canvas.addEventListener( 'pointermove', e => { ctx.lineTo(e.offsetX, e.offsetY) ctx.stroke() } )

实际开发建议使用:

PointerEvent

而不是分别处理:

mousedown mousemove touchstart touchmove

因为 Pointer Events 可以统一鼠标、触摸、笔。


二十五、公司实际场景 ⑧:画板

类似:

Photoshop 白板 在线涂鸦 流程图 电子签名

基本结构:

Canvas ↓ PointerDown ↓ 开始路径 PointerMove ↓ 不断 lineTo PointerUp ↓ 结束

例如:

let drawing = false canvas.addEventListener('pointerdown', e => { drawing = true ctx.beginPath() ctx.moveTo(e.offsetX, e.offsetY) }) canvas.addEventListener('pointermove', e => { if (!drawing) return ctx.lineTo(e.offsetX, e.offsetY) ctx.stroke() }) canvas.addEventListener('pointerup', () => { drawing = false })

二十六、公司实际场景 ⑨:图表

例如:

销量 │ │ ● │ ● │ ● │ ● └────────────────

可以自己使用 Canvas 实现:

  • 折线图

  • 柱状图

  • 饼图

  • 雷达图

  • 热力图

不过公司实际开发通常:

业务图表 ↓ ECharts / AntV 等 ↓ 底层可能使用 Canvas/SVG

所以你不一定需要手写完整图表库。

但是理解 Canvas 有助于理解:

为什么 ECharts Canvas 模式能够渲染大量数据。


二十七、公司实际场景 ⑩:动画

Canvas 非常适合动画。

核心:

requestAnimationFrame()

例如小球:

let x = 0 function render() { ctx.clearRect( 0, 0, canvas.width, canvas.height ) ctx.beginPath() ctx.arc( x, 100, 20, 0, Math.PI * 2 ) ctx.fill() x += 2 requestAnimationFrame(render) } render()

动画流程:

requestAnimationFrame ↓ 清空 Canvas ↓ 更新状态 ↓ 重新绘制 ↓ requestAnimationFrame ↓ ...

二十八、为什么动画不用 setInterval?

不推荐:

setInterval(() => { render() }, 16)

推荐:

requestAnimationFrame(render)

因为它更适合浏览器渲染节奏。

基本思想:

JS 更新 ↓ 浏览器下一帧 ↓ 绘制

二十九、Canvas 动画性能

这是公司面试非常喜欢问的。

假设:

10000 个粒子

每一帧都:

clearRect() for (...) { draw() }

主线程压力可能很大。

优化方向:

1. 减少绘制数量

10000 → 3000

2. 减少状态切换

不要:

fillStyle = 'red' draw() fillStyle = 'blue' draw() fillStyle = 'red' draw()

尽量:

fillStyle = 'red' for (...) { draw() }

三十、批量绘制

例如:

ctx.beginPath() for (const point of points) { ctx.moveTo(point.x, point.y) ctx.arc( point.x, point.y, 2, 0, Math.PI * 2 ) } ctx.fill()

相比大量:

beginPath() fill() beginPath() fill()

通常更合理。


三十一、Canvas 高 DPI / 高清屏

这是实际开发非常重要的一块。

例如:

普通屏幕 devicePixelRatio = 1 Retina devicePixelRatio = 2

如果直接:

<canvas width="500" height="300">

可能出现:

文字模糊 线条模糊 图片模糊

常见解决方案:

const dpr = window.devicePixelRatio || 1 const rect = canvas.getBoundingClientRect() canvas.width = rect.width * dpr canvas.height = rect.height * dpr ctx.scale(dpr, dpr)

这样:

CSS: 500 × 300 实际 Canvas: 1000 × 600

Retina 屏幕就能得到更好的清晰度。


三十二、但是这里有一个坑

如果你:

canvas.width = 1000 canvas.height = 600

然后:

canvas { width: 500px; height: 300px; }

那么你必须明确:

逻辑坐标 500 × 300 实际像素 1000 × 600

所以项目里通常要把:

CSS 尺寸 逻辑尺寸 Canvas bitmap 尺寸

区分开。


三十三、Canvas 和 DOM 事件

Canvas 本身是一个元素:

<canvas></canvas>

所以可以监听:

canvas.addEventListener('click', ...)

但是 Canvas 内部:

你画的矩形 你画的圆 你画的文字

都不是 DOM。

所以:

document.querySelector('.circle')

找不到你画的圆。


三十四、Canvas 如何实现点击某个图形?

需要自己做:

鼠标坐标 ↓ 判断 ↓ 是否落在图形区域

例如圆:

const dx = mouseX - circle.x const dy = mouseY - circle.y const distance = Math.sqrt( dx * dx + dy * dy ) if (distance <= circle.radius) { console.log('点击了圆') }

这叫:

Hit Testing / 命中检测

Canvas 编辑器、白板、图形编辑器都会大量使用。


三十五、矩形命中检测

function isPointInRect( x, y, rect ) { return ( x >= rect.x && x <= rect.x + rect.width && y >= rect.y && y <= rect.y + rect.height ) }

三十六、一个非常实用的技巧:isPointInPath

Canvas 自己提供了路径命中检测:

const path = new Path2D() path.rect(100, 100, 200, 100) ctx.fill(path) canvas.addEventListener('click', e => { const rect = canvas.getBoundingClientRect() const x = e.clientX - rect.left const y = e.clientY - rect.top if (ctx.isPointInPath(path, x, y)) { console.log('点击到了') } })

这个在 Canvas 图形编辑器里很好用。


三十七、Canvas 图片跨域问题

这是面试必问坑点之一

假设:

const img = new Image() img.src = 'https://xxx.com/a.jpg' img.onload = () => { ctx.drawImage(img, 0, 0) canvas.toBlob(...) }

可能报:

SecurityError

原因:

跨域图片没有正确 CORS 授权,Canvas 被污染(tainted)。

一旦 Canvas 被污染:

canvas.toBlob() canvas.toDataURL() ctx.getImageData()

都可能抛出SecurityError。(MDN Web Docs)


三十八、怎么解决?

前端:

const img = new Image() img.crossOrigin = 'anonymous' img.src = url

但是光前端这样写不够

服务端还需要:

Access-Control-Allow-Origin: https://your-site.com

或者合适的允许来源配置。

所以:

Canvas 跨域图片问题是“前端 + 服务端 CORS”共同解决的。

MDN 也明确说明了crossorigin属性需要和服务端 CORS 响应头配合。(MDN Web Docs)


三十九、Canvas 清空的几个方式

clearRect

ctx.clearRect( 0, 0, canvas.width, canvas.height )

最常用。


重置 width

canvas.width = canvas.width

也能清空。

但是会:

重置 Canvas 的绘图状态。

所以一般不推荐作为普通清屏手段。


四十、Canvas 撤销 / 重做怎么实现?

Canvas 不自带:

undo redo

公司项目一般自己做。

方案一:

保存图片:

const history = [] history.push( canvas.toDataURL() )

撤销:

history.pop() ↓ 重新 drawImage

但是:

数据量大时非常占内存。


更好的方式:

保存操作:

[ { type: 'line', points: [...] }, { type: 'circle', x: 100, y: 100 } ]

然后:

state ↓ 重新 render ↓ Canvas

这其实就是:

Canvas + 状态模型

更适合复杂编辑器。


四十一、Canvas 和 Web Worker

如果 Canvas 任务很重:

大量粒子 复杂图片处理 大图处理 复杂绘制

可以考虑:

主线程 ↓ Worker ↓ OffscreenCanvas

OffscreenCanvas可以让 Canvas 渲染脱离 DOM,并支持在 Worker 中进行渲染,从而把一部分重任务从主线程移走。(MDN Web Docs)

基本:

const offscreen = canvas.transferControlToOffscreen() const worker = new Worker('./worker.js') worker.postMessage( { canvas: offscreen }, [offscreen] )

Worker:

self.onmessage = e => { const canvas = e.data.canvas const ctx = canvas.getContext('2d') // 绘制 }

这个属于:

Canvas 性能进阶。

不是普通业务一开始就需要。


四十二、Canvas 性能优化总结

公司实际开发可以记这一套:

Canvas 性能 │ ├── 减少绘制次数 │ ├── 减少状态切换 │ ├── 批量绘制 │ ├── requestAnimationFrame │ ├── 避免频繁创建对象 │ ├── 控制 Canvas 分辨率 │ ├── 大量计算放 Worker │ ├── OffscreenCanvas │ └── 必要时使用 WebGL

四十三、Canvas 和 SVG 怎么选?

这个也是面试高频。

CanvasSVG
本质位图矢量 DOM
元素数量多时更适合太多可能压力大
单个元素操作不方便很方便
事件自己做命中检测DOM 事件
放大可能失真不失真
图片处理很强一般
游戏很适合一般
图片编辑很适合一般
图标一般很适合
流程图可以很适合
数据可视化很适合也很适合

简单记:

大量像素 大量绘制 动画 图片处理 游戏 ↓ Canvas
大量独立图形 需要点击单个元素 需要 DOM 结构 需要无损缩放 ↓ SVG

四十四、Canvas 和 WebGL 怎么选?

进一步:

Canvas 2D ↓ CPU / 浏览器 2D 绘制模型 WebGL ↓ GPU ↓ 大量图形 / 3D / 高性能

例如:

普通图片处理 签名 海报 验证码 简单动画 → Canvas 2D
3D 大量粒子 复杂游戏 地图 GPU 大规模计算 → WebGL / WebGPU

四十五、公司项目最常见的 Canvas 场景

你可以直接记这张表:

场景Canvas 使用
图片压缩⭐⭐⭐⭐⭐
图片裁剪⭐⭐⭐⭐⭐
图片格式转换⭐⭐⭐⭐⭐
图片水印⭐⭐⭐⭐⭐
图片拼接⭐⭐⭐⭐
签名板⭐⭐⭐⭐⭐
画板⭐⭐⭐⭐⭐
验证码⭐⭐⭐⭐
海报生成⭐⭐⭐⭐⭐
截图⭐⭐⭐⭐
Canvas 图表⭐⭐⭐⭐
粒子动画⭐⭐⭐⭐⭐
游戏⭐⭐⭐⭐⭐
视频帧处理⭐⭐⭐⭐
PDF/文档预览中的图形处理⭐⭐⭐
OCR 前图片预处理⭐⭐⭐⭐
大数据可视化⭐⭐⭐⭐

四十六、如果你是做 Vue / React

实际项目不要把 Canvas 所有逻辑直接塞组件里。

不推荐:

function App() { // 500 行 Canvas 代码 }

建议:

components/ CanvasEditor.jsx hooks/ useCanvas.js utils/ canvas/ image.js draw.js coordinate.js hitTest.js export.js

例如:

function useCanvas() { const canvasRef = useRef(null) function draw() { const canvas = canvasRef.current const ctx = canvas.getContext('2d') // ... } return { canvasRef, draw } }

Vue:

const canvasRef = ref(null) onMounted(() => { const canvas = canvasRef.value const ctx = canvas.getContext('2d') })

四十七、公司实际开发推荐架构

如果做一个 Canvas 编辑器:

React/Vue │ ↓ CanvasEditor │ ┌─────────┴─────────┐ ↓ ↓ State Canvas │ │ ↓ ↓ shapes / images render() │ │ └─────────┬─────────┘ ↓ Interaction │ ┌───────────┼───────────┐ ↓ ↓ ↓ Pointer HitTest Keyboard

核心思想:

不要把 Canvas 当成状态本身。

而应该:

业务状态 ↓ render ↓ Canvas

例如:

const shapes = [ { type: 'rect', x: 100, y: 100, width: 200, height: 100 }, { type: 'circle', x: 400, y: 200, radius: 50 } ]

然后:

function render(ctx, shapes) { ctx.clearRect( 0, 0, canvas.width, canvas.height ) for (const shape of shapes) { drawShape(ctx, shape) } }

这个思路非常重要。


四十八、一个真正比较像公司的 Canvas 项目

建议你练:

「图片编辑器」

功能:

┌─────────────────────────────┐ │ 图片编辑器 │ ├─────────────────────────────┤ │ │ │ Canvas │ │ │ │ ┌──────────┐ │ │ │ 图片 │ │ │ └──────────┘ │ │ │ ├─────────────────────────────┤ │ 上传 │ 裁剪 │ 旋转 │ 水印 │ │ 压缩 │ 撤销 │ 重做 │ 导出 │ └─────────────────────────────┘

你会一次性学会:

Canvas 基础 ↓ drawImage ↓ 图片缩放 ↓ 图片裁剪 ↓ 旋转 ↓ 坐标转换 ↓ 鼠标/触摸事件 ↓ 命中检测 ↓ 撤销/重做 ↓ toBlob ↓ 文件上传 ↓ 高 DPI ↓ 性能优化

这比单纯练:

画圆 画矩形 画线

有价值得多。


四十九、Canvas 面试重点

如果你准备前端面试,我建议重点掌握这15 个问题

基础

  1. Canvas 和 SVG 区别?

  2. getContext('2d')是什么?

  3. Canvas width/height 和 CSS width/height 有什么区别?

  4. fillRectstrokeRectclearRect

  5. beginPath()有什么作用?

  6. save()/restore()

  7. translate/rotate/scale

图片

  1. drawImage()三种参数形式?

  2. 如何用 Canvas 压缩图片?

  3. toBlob()toDataURL()区别?

  4. Canvas 图片跨域为什么会污染?

  5. Canvas 如何做图片裁剪?

性能

  1. Canvas 如何实现动画?

  2. 为什么用requestAnimationFrame

  3. Canvas 卡顿怎么优化?

再往上:

Canvas ↓ OffscreenCanvas ↓ Web Worker ↓ WebGL ↓ WebGPU

属于进阶路线。


五十、你可以按这个顺序学

如果你的目标是公司实际开发 + 面试,我建议不要平均用力:

第一阶段:基础 │ ├── Canvas 创建 ├── 坐标系 ├── 矩形 ├── Path ├── 圆 ├── 文字 ├── 渐变 └── save / restore ↓ 第二阶段:图片 │ ├── Image ├── drawImage ├── 图片缩放 ├── 图片裁剪 ├── toBlob ├── toDataURL ├── 图片压缩 ├── WebP └── 水印 ↓ 第三阶段:交互 │ ├── PointerEvent ├── 坐标转换 ├── 拖动 ├── 缩放 ├── 旋转 ├── HitTest └── isPointInPath ↓ 第四阶段:动画 │ ├── requestAnimationFrame ├── 动画状态 ├── 粒子 └── 性能优化 ↓ 第五阶段:工程化 │ ├── React/Vue ├── 高 DPI ├── 撤销/重做 ├── 状态管理 ├── Worker └── OffscreenCanvas

其中对普通前端岗位来说,最值得你重点掌握的是:

drawImage+ 图片压缩/裁剪 +toBlob+ PointerEvent + 坐标转换 + 高 DPI + RAF + 跨域 + Canvas 性能。

尤其是你之前做的前端图片预处理 → 压缩 → WebP → OSS → AI这条链路,Canvas 正好是其中非常核心的一环。实际生产里要特别注意超大图片导致的内存/主线程阻塞、CORS、EXIF 方向、输出质量和文件大小这些问题;其中跨域图片如果没有 CORS 授权,会直接影响toBlob()toDataURL()等读取操作。(MDN Web Docs)

如果按“真正能拿去面试”的方式继续,我建议下一步直接做一套《Canvas 10 个公司实战案例》图片压缩 → 裁剪器 → 图片旋转 → 水印 → 签名板 → 橡皮擦 → 撤销/重做 → 海报生成器 → Canvas 粒子动画 → React/Vue Canvas 编辑器,每个案例都带完整代码和面试考点。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询