深入学习Canvas性能优化技术,包括离屏渲染、分层渲染、内存管理等核心优化策略 性能优化是Canvas应用开发中的关键环节。本章将深入探讨各种性能优化技术,帮助你创建流畅、高效的Canvas应用。
Canvas性能优化的核心目标是减少CPU和GPU的负载,提高渲染效率。主要优化方向包括:
| 优化方向 | 描述 | 效果 |
|---|---|---|
| 离屏渲染 | 使用离屏Canvas预渲染 | 减少重复绘制 |
| 分层渲染 | 多Canvas分层处理 | 减少重绘区域 |
| 状态优化 | 减少上下文状态切换 | 降低API调用开销 |
| 区域优化 | 只重绘变化区域 | 减少像素处理 |
| 动画优化 | 合理使用动画帧 | 平滑动画效果 |
| 内存管理 | 及时释放资源 | 避免内存泄漏 |
class PerformanceMonitor {
constructor() {
this.metrics = {
fps: 0,
frameTime: 0,
drawCalls: 0,
memory: 0
}
this.frames = []
this.lastTime = performance.now()
this.frameCount = 0
}
beginFrame() {
this.frameStartTime = performance.now()
}
endFrame() {
const now = performance.now()
const frameTime = now - this.frameStartTime
this.frames.push(frameTime)
if (this.frames.length > 60) {
this.frames.shift()
}
this.frameCount++
if (now - this.lastTime >= 1000) {
this.metrics.fps = this.frameCount
this.metrics.frameTime = this.frames.reduce((a, b) => a + b, 0) / this.frames.length
this.metrics.drawCalls = this.drawCalls
this.metrics.memory = performance.memory
? performance.memory.usedJSHeapSize / 1024 / 1024
: 0
this.frameCount = 0
this.drawCalls = 0
this.lastTime = now
}
}
recordDrawCall() {
this.drawCalls++
}
getMetrics() {
return { ...this.metrics }
}
render(ctx, x = 10, y = 10) {
const metrics = this.getMetrics()
ctx.save()
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'
ctx.fillRect(x, y, 180, 90)
ctx.fillStyle = '#00ff00'
ctx.font = '12px monospace'
ctx.textBaseline = 'top'
ctx.fillText(`FPS: ${metrics.fps}`, x + 10, y + 10)
ctx.fillText(`Frame: ${metrics.frameTime.toFixed(2)}ms`, x + 10, y + 28)
ctx.fillText(`Draw Calls: ${metrics.drawCalls}`, x + 10, y + 46)
ctx.fillText(`Memory: ${metrics.memory.toFixed(2)}MB`, x + 10, y + 64)
ctx.restore()
}
}
class PerformanceMarker {
constructor() {
this.marks = new Map()
this.measures = []
}
mark(name) {
this.marks.set(name, performance.now())
if (typeof performance !== 'undefined' && performance.mark) {
performance.mark(name)
}
}
measure(name, startMark, endMark) {
const start = this.marks.get(startMark)
const end = this.marks.get(endMark)
if (start !== undefined && end !== undefined) {
const duration = end - start
this.measures.push({ name, duration, start, end })
return duration
}
return 0
}
getMeasures() {
return [...this.measures]
}
clear() {
this.marks.clear()
this.measures = []
}
report() {
console.table(this.measures.map(m => ({
Name: m.name,
Duration: `${m.duration.toFixed(2)}ms`,
Start: `${m.start.toFixed(2)}ms`,
End: `${m.end.toFixed(2)}ms`
})))
}
}
class OptimizedRenderer {
constructor(canvas) {
this.canvas = canvas
this.ctx = canvas.getContext('2d')
this.dirtyRects = []
this.lastRenderState = null
}
addDirtyRect(x, y, width, height) {
this.dirtyRects.push({ x, y, width, height })
}
mergeDirtyRects() {
if (this.dirtyRects.length === 0) return null
let minX = Infinity, minY = Infinity
let maxX = -Infinity, maxY = -Infinity
this.dirtyRects.forEach(rect => {
minX = Math.min(minX, rect.x)
minY = Math.min(minY, rect.y)
maxX = Math.max(maxX, rect.x + rect.width)
maxY = Math.max(maxY, rect.y + rect.height)
})
this.dirtyRects = []
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY }
}
render(objects) {
const dirtyRect = this.mergeDirtyRects()
if (dirtyRect) {
this.ctx.save()
this.ctx.beginPath()
this.ctx.rect(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height)
this.ctx.clip()
this.ctx.clearRect(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height)
objects.forEach(obj => {
if (this.intersects(obj, dirtyRect)) {
obj.render(this.ctx)
}
})
this.ctx.restore()
}
}
intersects(obj, rect) {
return obj.x < rect.x + rect.width &&
obj.x + obj.width > rect.x &&
obj.y < rect.y + rect.height &&
obj.y + obj.height > rect.y
}
}
class BatchRenderer {
constructor(ctx) {
this.ctx = ctx
this.batches = new Map()
}
addToBatch(type, renderData) {
if (!this.batches.has(type)) {
this.batches.set(type, [])
}
this.batches.get(type).push(renderData)
}
flush() {
this.batches.forEach((items, type) => {
this.ctx.save()
items.forEach(item => {
this.renderItem(type, item)
})
this.ctx.restore()
})
this.batches.clear()
}
renderItem(type, item) {
switch (type) {
case 'rect':
if (item.fillStyle) this.ctx.fillStyle = item.fillStyle
this.ctx.fillRect(item.x, item.y, item.width, item.height)
break
case 'circle':
this.ctx.beginPath()
this.ctx.arc(item.x, item.y, item.radius, 0, Math.PI * 2)
if (item.fillStyle) {
this.ctx.fillStyle = item.fillStyle
this.ctx.fill()
}
break
case 'line':
this.ctx.beginPath()
this.ctx.moveTo(item.x1, item.y1)
this.ctx.lineTo(item.x2, item.y2)
if (item.strokeStyle) this.ctx.strokeStyle = item.strokeStyle
if (item.lineWidth) this.ctx.lineWidth = item.lineWidth
this.ctx.stroke()
break
}
}
}
class ObjectPool {
constructor(factory, initialSize = 100) {
this.factory = factory
this.pool = []
this.active = new Set()
for (let i = 0; i < initialSize; i++) {
this.pool.push(factory())
}
}
acquire() {
let obj
if (this.pool.length > 0) {
obj = this.pool.pop()
} else {
obj = this.factory()
}
this.active.add(obj)
return obj
}
release(obj) {
if (this.active.has(obj)) {
this.active.delete(obj)
if (typeof obj.reset === 'function') {
obj.reset()
}
this.pool.push(obj)
}
}
releaseAll() {
this.active.forEach(obj => {
if (typeof obj.reset === 'function') {
obj.reset()
}
this.pool.push(obj)
})
this.active.clear()
}
getStats() {
return {
pooled: this.pool.length,
active: this.active.size,
total: this.pool.length + this.active.size
}
}
}
class Particle {
constructor() {
this.reset()
}
reset() {
this.x = 0
this.y = 0
this.vx = 0
this.vy = 0
this.life = 0
this.maxLife = 0
this.size = 0
this.color = '#ffffff'
}
init(x, y, vx, vy, life, size, color) {
this.x = x
this.y = y
this.vx = vx
this.vy = vy
this.life = life
this.maxLife = life
this.size = size
this.color = color
}
update(dt) {
this.x += this.vx * dt
this.y += this.vy * dt
this.life -= dt
return this.life > 0
}
render(ctx) {
const alpha = this.life / this.maxLife
ctx.globalAlpha = alpha
ctx.fillStyle = this.color
ctx.beginPath()
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2)
ctx.fill()
}
}
const particlePool = new ObjectPool(() => new Particle(), 500)
const performanceChecklist = {
rendering: [
'使用离屏Canvas预渲染静态内容',
'实现分层渲染减少重绘',
'使用脏矩形技术只更新变化区域',
'批量处理相同类型的绘制操作',
'避免过度使用阴影和模糊效果'
],
state: [
'减少fillStyle/strokeStyle切换次数',
'合理使用save/restore',
'避免在循环中设置相同状态',
'使用路径批量绘制',
'减少字体设置次数'
],
animation: [
'使用requestAnimationFrame',
'实现帧率控制',
'跳过不必要的帧',
'使用CSS transform处理简单动画',
'避免同时运行过多动画'
],
memory: [
'及时释放不再使用的对象',
'使用对象池复用对象',
'避免创建临时对象',
'清除不再使用的Canvas引用',
'监控内存使用情况'
],
data: [
'使用TypedArray处理大量数据',
'避免在渲染循环中创建数组',
'使用空间索引加速碰撞检测',
'缓存计算结果',
'使用Web Worker处理复杂计算'
]
}
Canvas性能优化是一个系统工程,需要从多个维度综合考虑:
通过合理运用这些优化技术,可以显著提升Canvas应用的性能,实现流畅的用户体验。