Canvas 常见问题
本文档收集 Canvas 开发中的常见问题及其解决方案。
显示问题
Canvas 显示模糊
问题描述: 在高 DPI 屏幕上,Canvas 内容显示模糊。
原因: Canvas 的实际分辨率与 CSS 显示尺寸不匹配。
解决方案:
function setupHiDPICanvas(canvas) {
const dpr = window.devicePixelRatio || 1
const rect = canvas.getBoundingClientRect()
// 设置实际分辨率
canvas.width = rect.width * dpr
canvas.height = rect.height * dpr
// 设置显示尺寸
canvas.style.width = rect.width + 'px'
canvas.style.height = rect.height + 'px'
// 缩放上下文
const ctx = canvas.getContext('2d')
ctx.scale(dpr, dpr)
return ctx
}图形边缘锯齿
问题描述: 线条或图形边缘有锯齿。
解决方案:
// 方法1:偏移 0.5 像素
ctx.moveTo(50.5, 10.5)
ctx.lineTo(200.5, 10.5)
// 方法2:使用更粗的线条
ctx.lineWidth = 2
// 方法3:启用抗锯齿(默认启用)
// Canvas 默认启用抗锯齿线条宽度不一致
问题描述: 1px 线条看起来是 2px。
原因: 线条以中心点绘制,半像素导致模糊。
解决方案:
// 奇数线宽使用半像素偏移
if (lineWidth % 2 === 1) {
ctx.translate(0.5, 0.5)
}图形不显示
问题描述: 绘制了图形但看不到。
排查步骤:
// 1. 检查坐标是否在画布范围内
console.log('Canvas size:', canvas.width, canvas.height)
console.log('Draw position:', x, y)
// 2. 检查样式是否设置
console.log('Fill style:', ctx.fillStyle)
console.log('Stroke style:', ctx.strokeStyle)
// 3. 检查透明度
console.log('Global alpha:', ctx.globalAlpha)
// 4. 检查是否有裁剪区域
ctx.save()
ctx.restore()
// 5. 检查 Canvas 是否被遮挡
console.log('Canvas display:', getComputedStyle(canvas).display)图像问题
图像无法加载
问题描述: drawImage() 不显示图像。
原因: 图像未加载完成。
解决方案:
const img = new Image()
img.onload = function() {
ctx.drawImage(img, 0, 0)
}
img.onerror = function() {
console.error('Image failed to load')
}
img.src = 'image.jpg'跨域图像问题
问题描述: 使用 getImageData() 或 toDataURL() 时报错。
错误信息: SecurityError: The operation is insecure.
解决方案:
// 方法1:设置 crossOrigin 属性
const img = new Image()
img.crossOrigin = 'anonymous'
img.src = 'https://example.com/image.jpg'
// 服务器需要返回 CORS 头:
// Access-Control-Allow-Origin: *
// 方法2:使用代理服务器
img.src = '/proxy?url=https://example.com/image.jpg'图像颜色失真
问题描述: 绘制的图像颜色与原图不符。
原因: Canvas 使用预乘 Alpha。
解决方案:
// 创建不带 Alpha 通道的上下文
const ctx = canvas.getContext('2d', { alpha: false })大图像处理
问题描述: 大图像加载慢或崩溃。
解决方案:
// 分块加载大图像
function loadLargeImage(src, chunkSize = 1024) {
const img = new Image()
img.onload = function() {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = chunkSize
canvas.height = chunkSize
for (let y = 0; y < img.height; y += chunkSize) {
for (let x = 0; x < img.width; x += chunkSize) {
const w = Math.min(chunkSize, img.width - x)
const h = Math.min(chunkSize, img.height - y)
ctx.clearRect(0, 0, chunkSize, chunkSize)
ctx.drawImage(img, x, y, w, h, 0, 0, w, h)
// 处理每个块...
}
}
}
img.src = src
}性能问题
动画卡顿
问题描述: 动画不流畅,FPS 低。
诊断:
// FPS 监控
let lastTime = performance.now()
let frames = 0
function measureFPS() {
frames++
const currentTime = performance.now()
if (currentTime - lastTime >= 1000) {
console.log(`FPS: ${frames}`)
frames = 0
lastTime = currentTime
}
requestAnimationFrame(measureFPS)
}解决方案:
- 使用
requestAnimationFrame - 使用离屏 Canvas 缓存
- 减少绘制调用
- 使用脏矩形渲染
// 使用脏矩形
const dirtyRects = []
function addDirtyRect(x, y, w, h) {
dirtyRects.push({ x, y, w, h })
}
function render() {
dirtyRects.forEach(rect => {
ctx.clearRect(rect.x, rect.y, rect.w, rect.h)
// 只重绘脏区域
})
dirtyRects.length = 0
}内存泄漏
问题描述: 页面内存持续增长。
排查:
// 1. 事件监听器未移除
// ❌ 错误
class Component {
constructor(canvas) {
canvas.addEventListener('click', this.onClick)
}
}
// ✅ 正确
class Component {
constructor(canvas) {
this.canvas = canvas
canvas.addEventListener('click', this.onClick)
}
destroy() {
this.canvas.removeEventListener('click', this.onClick)
}
}
// 2. 动画未停止
// ✅ 停止动画
function destroy() {
cancelAnimationFrame(animationId)
}
// 3. ImageData 未释放
// ✅ 使用对象池
const imageDataPool = []大量绘制性能差
问题描述: 绘制大量图形时性能下降。
解决方案:
// 批量绘制
ctx.beginPath()
for (let i = 0; i < 1000; i++) {
ctx.moveTo(x[i], y[i])
ctx.arc(x[i], y[i], radius, 0, Math.PI * 2)
}
ctx.fill()
// 使用 Path2D
const path = new Path2D()
// 添加路径...
ctx.fill(path)交互问题
点击坐标不准确
问题描述: 点击位置与实际绘制位置不符。
原因: 未考虑 Canvas 边界和缩放。
解决方案:
function getMousePos(canvas, e) {
const rect = canvas.getBoundingClientRect()
const scaleX = canvas.width / rect.width
const scaleY = canvas.height / rect.height
return {
x: (e.clientX - rect.left) * scaleX,
y: (e.clientY - rect.top) * scaleY
}
}触摸事件问题
问题描述: 触摸时页面滚动或缩放。
解决方案:
canvas.addEventListener('touchstart', (e) => {
e.preventDefault()
// 处理触摸
}, { passive: false })
canvas.addEventListener('touchmove', (e) => {
e.preventDefault()
// 处理移动
}, { passive: false })图形选择不准确
问题描述: 点击图形时判断错误。
解决方案:
// 使用 isPointInPath
function isPointInShape(x, y) {
// 重建路径
ctx.beginPath()
ctx.arc(shape.x, shape.y, shape.radius, 0, Math.PI * 2)
return ctx.isPointInPath(x, y)
}
// 或使用距离计算
function isPointInCircle(px, py, cx, cy, r) {
const dx = px - cx
const dy = py - cy
return dx * dx + dy * dy <= r * r
}兼容性问题
IE 不支持某些功能
问题描述: 某些 API 在 IE 中不可用。
解决方案:
// Polyfill
if (!CanvasRenderingContext2D.prototype.roundRect) {
CanvasRenderingContext2D.prototype.roundRect = function(x, y, w, h, r) {
if (w < 2 * r) r = w / 2
if (h < 2 * r) r = h / 2
this.moveTo(x + r, y)
this.arcTo(x + w, y, x + w, y + h, r)
this.arcTo(x + w, y + h, x, y + h, r)
this.arcTo(x, y + h, x, y, r)
this.arcTo(x, y, x + w, y, r)
return this
}
}iOS Safari 图像限制
问题描述: 大图像在 iOS Safari 中无法显示。
原因: iOS Safari 有图像大小限制。
解决方案:
function checkCanvasSize(width, height) {
const maxPixels = 16777216 // 16MP for iOS
const pixels = width * height
if (pixels > maxPixels) {
console.warn('Canvas size exceeds iOS limit')
// 缩小尺寸
const scale = Math.sqrt(maxPixels / pixels)
return {
width: Math.floor(width * scale),
height: Math.floor(height * scale)
}
}
return { width, height }
}Firefox 性能问题
问题描述: Firefox 中某些操作性能差。
解决方案:
// 避免频繁的 getImageData
// 使用缓存
const imageDataCache = new Map()
function getImageDataCached(x, y, w, h) {
const key = `${x},${y},${w},${h}`
if (!imageDataCache.has(key)) {
imageDataCache.set(key, ctx.getImageData(x, y, w, h))
}
return imageDataCache.get(key)
}其他问题
toDataURL 返回空白
问题描述: canvas.toDataURL() 返回空白图像。
原因: Canvas 内容为空或跨域污染。
解决方案:
// 检查是否有内容
console.log('Canvas width:', canvas.width)
console.log('Canvas height:', canvas.height)
// 确保已绘制内容
ctx.fillStyle = 'red'
ctx.fillRect(0, 0, 10, 10)
// 检查是否跨域污染
try {
canvas.toDataURL()
} catch (e) {
console.error('Canvas tainted:', e)
}save/restore 不生效
问题描述: 状态保存/恢复后仍受影响。
原因: save() 和 restore() 调用不匹配。
解决方案:
// 使用计数确保匹配
let saveCount = 0
function safeSave() {
ctx.save()
saveCount++
}
function safeRestore() {
if (saveCount > 0) {
ctx.restore()
saveCount--
}
}
// 或使用 try-finally
function withState(callback) {
ctx.save()
try {
callback()
} finally {
ctx.restore()
}
}渐变颜色不对
问题描述: 渐变颜色与预期不符。
原因: 渐变坐标设置错误。
解决方案:
// 确保渐变范围覆盖绘制区域
const gradient = ctx.createLinearGradient(
x, y, // 起点
x + width, y // 终点
)
// 对于圆形渐变
const radialGradient = ctx.createRadialGradient(
cx, cy, innerRadius, // 内圆
cx, cy, outerRadius // 外圆
)文字显示问题
问题描述: 文字模糊或样式不正确。
解决方案:
// 1. 确保字体格式正确
ctx.font = 'bold 20px Arial, sans-serif'
// 2. 处理高 DPI
const dpr = window.devicePixelRatio || 1
ctx.font = `${20 * dpr}px Arial`
// 3. 使用 textBaseline 对齐
ctx.textBaseline = 'top'
ctx.textAlign = 'left'调试技巧
绘制边界框
function drawBoundingBox(x, y, width, height) {
ctx.strokeStyle = 'red'
ctx.lineWidth = 1
ctx.strokeRect(x, y, width, height)
}显示坐标系
function drawCoordinateSystem() {
ctx.save()
ctx.strokeStyle = 'rgba(0, 0, 0, 0.2)'
ctx.lineWidth = 1
// 绘制网格
for (let x = 0; x < canvas.width; x += 50) {
ctx.beginPath()
ctx.moveTo(x, 0)
ctx.lineTo(x, canvas.height)
ctx.stroke()
}
for (let y = 0; y < canvas.height; y += 50) {
ctx.beginPath()
ctx.moveTo(0, y)
ctx.lineTo(canvas.width, y)
ctx.stroke()
}
// 绘制坐标轴
ctx.strokeStyle = 'red'
ctx.lineWidth = 2
ctx.beginPath()
ctx.moveTo(0, 0)
ctx.lineTo(canvas.width, 0)
ctx.moveTo(0, 0)
ctx.lineTo(0, canvas.height)
ctx.stroke()
ctx.restore()
}日志输出
function logDraw(method, ...args) {
console.log(`Drawing ${method}:`, args)
ctx[method](...args)
}
// 使用
logDraw('fillRect', 10, 10, 100, 50)相关资源
返回:Canvas 教程目录 | 上一篇:最佳实践