状态优化

Canvas上下文状态切换(如fillStyle、strokeStyle、font等)会带来一定的性能开销。合理管理状态切换可以显著提升渲染性能。

状态切换开销

Canvas状态类型

const canvasStateTypes = {
  fillStyle: {
    type: 'color',
    cost: 'medium',
    description: '填充颜色或样式'
  },
  strokeStyle: {
    type: 'color',
    cost: 'medium',
    description: '描边颜色或样式'
  },
  lineWidth: {
    type: 'number',
    cost: 'low',
    description: '线条宽度'
  },
  lineCap: {
    type: 'enum',
    cost: 'low',
    description: '线条端点样式'
  },
  lineJoin: {
    type: 'enum',
    cost: 'low',
    description: '线条连接样式'
  },
  miterLimit: {
    type: 'number',
    cost: 'low',
    description: '斜接限制'
  },
  font: {
    type: 'string',
    cost: 'high',
    description: '字体设置'
  },
  textAlign: {
    type: 'enum',
    cost: 'low',
    description: '文本对齐'
  },
  textBaseline: {
    type: 'enum',
    cost: 'low',
    description: '文本基线'
  },
  globalAlpha: {
    type: 'number',
    cost: 'medium',
    description: '全局透明度'
  },
  globalCompositeOperation: {
    type: 'enum',
    cost: 'high',
    description: '合成操作'
  },
  shadowColor: {
    type: 'color',
    cost: 'medium',
    description: '阴影颜色'
  },
  shadowBlur: {
    type: 'number',
    cost: 'medium',
    description: '阴影模糊'
  },
  shadowOffsetX: {
    type: 'number',
    cost: 'low',
    description: '阴影X偏移'
  },
  shadowOffsetY: {
    type: 'number',
    cost: 'low',
    description: '阴影Y偏移'
  },
  lineDash: {
    type: 'array',
    cost: 'medium',
    description: '虚线模式'
  }
}

状态切换性能测试

class StateSwitchBenchmark {
  constructor(ctx) {
    this.ctx = ctx
    this.results = []
  }
  
  benchmarkFillStyle(iterations = 10000) {
    const ctx = this.ctx
    
    const startNoSwitch = performance.now()
    ctx.fillStyle = '#ff0000'
    for (let i = 0; i < iterations; i++) {
      ctx.fillRect(0, 0, 10, 10)
    }
    const timeNoSwitch = performance.now() - startNoSwitch
    
    const startWithSwitch = performance.now()
    for (let i = 0; i < iterations; i++) {
      ctx.fillStyle = `rgb(${i % 256}, 0, 0)`
      ctx.fillRect(0, 0, 10, 10)
    }
    const timeWithSwitch = performance.now() - startWithSwitch
    
    return {
      operation: 'fillStyle',
      iterations,
      noSwitchTime: timeNoSwitch.toFixed(2),
      withSwitchTime: timeWithSwitch.toFixed(2),
      overhead: ((timeWithSwitch - timeNoSwitch) / iterations * 1000).toFixed(4)
    }
  }
  
  benchmarkFont(iterations = 10000) {
    const ctx = this.ctx
    
    const startNoSwitch = performance.now()
    ctx.font = '16px Arial'
    for (let i = 0; i < iterations; i++) {
      ctx.fillText('Test', 0, 10)
    }
    const timeNoSwitch = performance.now() - startNoSwitch
    
    const startWithSwitch = performance.now()
    for (let i = 0; i < iterations; i++) {
      ctx.font = `${10 + (i % 20)}px Arial`
      ctx.fillText('Test', 0, 10)
    }
    const timeWithSwitch = performance.now() - startWithSwitch
    
    return {
      operation: 'font',
      iterations,
      noSwitchTime: timeNoSwitch.toFixed(2),
      withSwitchTime: timeWithSwitch.toFixed(2),
      overhead: ((timeWithSwitch - timeNoSwitch) / iterations * 1000).toFixed(4)
    }
  }
  
  runAllBenchmarks() {
    this.results.push(this.benchmarkFillStyle())
    this.results.push(this.benchmarkFont())
    return this.results
  }
}

状态管理器

智能状态管理

class StateManager {
  constructor(ctx) {
    this.ctx = ctx
    this.currentState = {}
    this.stateStack = []
    
    this.initStateTracking()
  }
  
  initStateTracking() {
    const trackedProperties = [
      'fillStyle', 'strokeStyle', 'lineWidth', 'lineCap', 'lineJoin',
      'miterLimit', 'font', 'textAlign', 'textBaseline', 'globalAlpha',
      'globalCompositeOperation', 'shadowColor', 'shadowBlur',
      'shadowOffsetX', 'shadowOffsetY'
    ]
    
    trackedProperties.forEach(prop => {
      this.currentState[prop] = this.ctx[prop]
    })
  }
  
  set(property, value) {
    if (this.currentState[property] !== value) {
      this.ctx[property] = value
      this.currentState[property] = value
      return true
    }
    return false
  }
  
  setMultiple(properties) {
    let changed = false
    Object.entries(properties).forEach(([key, value]) => {
      if (this.set(key, value)) {
        changed = true
      }
    })
    return changed
  }
  
  get(property) {
    return this.currentState[property]
  }
  
  push() {
    this.stateStack.push({ ...this.currentState })
  }
  
  pop() {
    if (this.stateStack.length > 0) {
      const state = this.stateStack.pop()
      this.setMultiple(state)
    }
  }
  
  reset() {
    this.ctx.setTransform(1, 0, 0, 1, 0, 0)
    this.ctx.globalAlpha = 1
    this.ctx.globalCompositeOperation = 'source-over'
    this.ctx.fillStyle = '#000000'
    this.ctx.strokeStyle = '#000000'
    this.ctx.lineWidth = 1
    this.ctx.lineCap = 'butt'
    this.ctx.lineJoin = 'miter'
    this.ctx.miterLimit = 10
    this.ctx.font = '10px sans-serif'
    this.ctx.textAlign = 'start'
    this.ctx.textBaseline = 'alphabetic'
    this.ctx.shadowColor = 'rgba(0, 0, 0, 0)'
    this.ctx.shadowBlur = 0
    this.ctx.shadowOffsetX = 0
    this.ctx.shadowOffsetY = 0
    
    this.initStateTracking()
  }
  
  getState() {
    return { ...this.currentState }
  }
  
  restoreState(state) {
    this.setMultiple(state)
  }
}

批量渲染优化

class BatchRenderOptimizer {
  constructor(ctx) {
    this.ctx = ctx
    this.stateManager = new StateManager(ctx)
    this.batches = []
    this.currentBatch = null
  }
  
  beginBatch(state) {
    this.currentBatch = {
      state,
      operations: []
    }
  }
  
  addOperation(operation) {
    if (this.currentBatch) {
      this.currentBatch.operations.push(operation)
    }
  }
  
  endBatch() {
    if (this.currentBatch) {
      this.batches.push(this.currentBatch)
      this.currentBatch = null
    }
  }
  
  flush() {
    this.batches.forEach(batch => {
      this.stateManager.setMultiple(batch.state)
      
      batch.operations.forEach(op => {
        this.executeOperation(op)
      })
    })
    
    this.batches = []
  }
  
  executeOperation(op) {
    switch (op.type) {
      case 'fillRect':
        this.ctx.fillRect(op.x, op.y, op.width, op.height)
        break
      case 'strokeRect':
        this.ctx.strokeRect(op.x, op.y, op.width, op.height)
        break
      case 'fillText':
        this.ctx.fillText(op.text, op.x, op.y)
        break
      case 'drawImage':
        this.ctx.drawImage(op.image, op.x, op.y)
        break
      case 'arc':
        this.ctx.beginPath()
        this.ctx.arc(op.x, op.y, op.radius, op.startAngle, op.endAngle)
        if (op.fill) {
          this.ctx.fill()
        } else {
          this.ctx.stroke()
        }
        break
    }
  }
  
  clear() {
    this.batches = []
    this.currentBatch = null
  }
}

按颜色分组渲染

颜色分组渲染器

class ColorGroupedRenderer {
  constructor(ctx) {
    this.ctx = ctx
    this.groups = new Map()
  }
  
  addRect(x, y, width, height, fillColor, strokeColor = null) {
    const fillKey = fillColor || 'transparent'
    
    if (!this.groups.has(fillKey)) {
      this.groups.set(fillKey, { fills: [], strokes: new Map() })
    }
    
    this.groups.get(fillKey).fills.push({ x, y, width, height })
    
    if (strokeColor) {
      const strokeKey = strokeColor
      if (!this.groups.get(fillKey).strokes.has(strokeKey)) {
        this.groups.get(fillKey).strokes.set(strokeKey, [])
      }
      this.groups.get(fillKey).strokes.get(strokeKey).push({ x, y, width, height })
    }
  }
  
  addCircle(x, y, radius, fillColor, strokeColor = null) {
    const fillKey = fillColor || 'transparent'
    
    if (!this.groups.has(fillKey)) {
      this.groups.set(fillKey, { fills: [], strokes: new Map() })
    }
    
    this.groups.get(fillKey).fills.push({ type: 'circle', x, y, radius })
    
    if (strokeColor) {
      const strokeKey = strokeColor
      if (!this.groups.get(fillKey).strokes.has(strokeKey)) {
        this.groups.get(fillKey).strokes.set(strokeKey, [])
      }
      this.groups.get(fillKey).strokes.get(strokeKey).push({ type: 'circle', x, y, radius })
    }
  }
  
  render() {
    this.groups.forEach((data, fillKey) => {
      if (fillKey !== 'transparent') {
        this.ctx.fillStyle = fillKey
        
        data.fills.forEach(item => {
          if (item.type === 'circle') {
            this.ctx.beginPath()
            this.ctx.arc(item.x, item.y, item.radius, 0, Math.PI * 2)
            this.ctx.fill()
          } else {
            this.ctx.fillRect(item.x, item.y, item.width, item.height)
          }
        })
      }
      
      data.strokes.forEach((items, strokeKey) => {
        this.ctx.strokeStyle = strokeKey
        
        items.forEach(item => {
          if (item.type === 'circle') {
            this.ctx.beginPath()
            this.ctx.arc(item.x, item.y, item.radius, 0, Math.PI * 2)
            this.ctx.stroke()
          } else {
            this.ctx.strokeRect(item.x, item.y, item.width, item.height)
          }
        })
      })
    })
    
    this.groups.clear()
  }
}

文本分组渲染

class TextGroupedRenderer {
  constructor(ctx) {
    this.ctx = ctx
    this.groups = new Map()
  }
  
  add(text, x, y, options = {}) {
    const {
      font = '16px Arial',
      fillStyle = '#000000',
      strokeStyle = null,
      lineWidth = 1,
      textAlign = 'left',
      textBaseline = 'top'
    } = options
    
    const key = `${font}|${fillStyle}|${strokeStyle}|${lineWidth}|${textAlign}|${textBaseline}`
    
    if (!this.groups.has(key)) {
      this.groups.set(key, {
        font,
        fillStyle,
        strokeStyle,
        lineWidth,
        textAlign,
        textBaseline,
        texts: []
      })
    }
    
    this.groups.get(key).texts.push({ text, x, y })
  }
  
  render() {
    this.groups.forEach(data => {
      this.ctx.font = data.font
      this.ctx.textAlign = data.textAlign
      this.ctx.textBaseline = data.textBaseline
      
      if (data.strokeStyle) {
        this.ctx.strokeStyle = data.strokeStyle
        this.ctx.lineWidth = data.lineWidth
        
        data.texts.forEach(item => {
          this.ctx.strokeText(item.text, item.x, item.y)
        })
      }
      
      this.ctx.fillStyle = data.fillStyle
      
      data.texts.forEach(item => {
        this.ctx.fillText(item.text, item.x, item.y)
      })
    })
    
    this.groups.clear()
  }
}

路径批量绘制

路径批处理器

class PathBatchProcessor {
  constructor(ctx) {
    this.ctx = ctx
    this.paths = []
    this.currentPath = null
  }
  
  beginPath(style = {}) {
    this.currentPath = {
      style,
      commands: []
    }
  }
  
  moveTo(x, y) {
    if (this.currentPath) {
      this.currentPath.commands.push({ type: 'moveTo', x, y })
    }
  }
  
  lineTo(x, y) {
    if (this.currentPath) {
      this.currentPath.commands.push({ type: 'lineTo', x, y })
    }
  }
  
  arc(x, y, radius, startAngle, endAngle, counterclockwise = false) {
    if (this.currentPath) {
      this.currentPath.commands.push({
        type: 'arc', x, y, radius, startAngle, endAngle, counterclockwise
      })
    }
  }
  
  arcTo(x1, y1, x2, y2, radius) {
    if (this.currentPath) {
      this.currentPath.commands.push({ type: 'arcTo', x1, y1, x2, y2, radius })
    }
  }
  
  quadraticCurveTo(cpx, cpy, x, y) {
    if (this.currentPath) {
      this.currentPath.commands.push({ type: 'quadraticCurveTo', cpx, cpy, x, y })
    }
  }
  
  bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
    if (this.currentPath) {
      this.currentPath.commands.push({ type: 'bezierCurveTo', cp1x, cp1y, cp2x, cp2y, x, y })
    }
  }
  
  closePath() {
    if (this.currentPath) {
      this.currentPath.commands.push({ type: 'closePath' })
    }
  }
  
  endPath(fill = true, stroke = false) {
    if (this.currentPath) {
      this.currentPath.fill = fill
      this.currentPath.stroke = stroke
      this.paths.push(this.currentPath)
      this.currentPath = null
    }
  }
  
  render() {
    const styleGroups = new Map()
    
    this.paths.forEach(path => {
      const key = JSON.stringify(path.style)
      if (!styleGroups.has(key)) {
        styleGroups.set(key, [])
      }
      styleGroups.get(key).push(path)
    })
    
    styleGroups.forEach((paths, key) => {
      const style = paths[0].style
      
      if (style.fillStyle) this.ctx.fillStyle = style.fillStyle
      if (style.strokeStyle) this.ctx.strokeStyle = style.strokeStyle
      if (style.lineWidth) this.ctx.lineWidth = style.lineWidth
      if (style.lineCap) this.ctx.lineCap = style.lineCap
      if (style.lineJoin) this.ctx.lineJoin = style.lineJoin
      
      paths.forEach(path => {
        this.ctx.beginPath()
        
        path.commands.forEach(cmd => {
          switch (cmd.type) {
            case 'moveTo':
              this.ctx.moveTo(cmd.x, cmd.y)
              break
            case 'lineTo':
              this.ctx.lineTo(cmd.x, cmd.y)
              break
            case 'arc':
              this.ctx.arc(cmd.x, cmd.y, cmd.radius, cmd.startAngle, cmd.endAngle, cmd.counterclockwise)
              break
            case 'arcTo':
              this.ctx.arcTo(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.radius)
              break
            case 'quadraticCurveTo':
              this.ctx.quadraticCurveTo(cmd.cpx, cmd.cpy, cmd.x, cmd.y)
              break
            case 'bezierCurveTo':
              this.ctx.bezierCurveTo(cmd.cp1x, cmd.cp1y, cmd.cp2x, cmd.cp2y, cmd.x, cmd.y)
              break
            case 'closePath':
              this.ctx.closePath()
              break
          }
        })
        
        if (path.fill) this.ctx.fill()
        if (path.stroke) this.ctx.stroke()
      })
    })
    
    this.paths = []
  }
}

实战示例

性能对比演示

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Canvas 状态优化 - 性能对比</title>
  <style>
    body {
      margin: 0;
      padding: 20px;
      background: #1a1a2e;
      font-family: system-ui, sans-serif;
    }
    
    .container {
      max-width: 900px;
      margin: 0 auto;
    }
    
    h1 {
      color: #eee;
      text-align: center;
    }
    
    .canvas-container {
      display: flex;
      gap: 20px;
      flex-wrap: wrap;
      justify-content: center;
    }
    
    .canvas-wrapper {
      background: #16213e;
      border-radius: 8px;
      padding: 10px;
    }
    
    .canvas-wrapper h3 {
      color: #eee;
      margin: 0 0 10px;
      text-align: center;
    }
    
    canvas {
      display: block;
      background: #0f0f23;
      border-radius: 4px;
    }
    
    .stats {
      color: #00ff00;
      font-family: monospace;
      font-size: 12px;
      margin-top: 10px;
      text-align: center;
    }
    
    .controls {
      display: flex;
      gap: 10px;
      justify-content: center;
      margin-top: 20px;
    }
    
    button {
      padding: 10px 20px;
      background: #e94560;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      font-size: 14px;
    }
    
    button:hover {
      background: #ff6b6b;
    }
  </style>
</head>
<body>
  <div class="container">
    <h1>状态切换性能对比</h1>
    
    <div class="canvas-container">
      <div class="canvas-wrapper">
        <h3>频繁状态切换</h3>
        <canvas id="unoptimizedCanvas" width="400" height="300"></canvas>
        <div class="stats" id="unoptimizedStats">渲染时间: 0ms</div>
      </div>
      
      <div class="canvas-wrapper">
        <h3>优化状态管理</h3>
        <canvas id="optimizedCanvas" width="400" height="300"></canvas>
        <div class="stats" id="optimizedStats">渲染时间: 0ms</div>
      </div>
    </div>
    
    <div class="controls">
      <button onclick="runTest()">运行测试</button>
      <button onclick="changeCount(1000)">1000对象</button>
      <button onclick="changeCount(5000)">5000对象</button>
      <button onclick="changeCount(10000)">10000对象</button>
    </div>
  </div>
  
  <script>
    const colors = ['#e94560', '#4ecdc4', '#45b7d1', '#96ceb4', '#ffeaa7', '#dfe6e9']
    
    function unoptimizedRender(ctx, objects) {
      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
      
      objects.forEach(obj => {
        ctx.fillStyle = obj.color
        ctx.strokeStyle = '#fff'
        ctx.lineWidth = 2
        ctx.font = '12px Arial'
        
        if (obj.type === 'rect') {
          ctx.fillRect(obj.x, obj.y, obj.width, obj.height)
          ctx.strokeRect(obj.x, obj.y, obj.width, obj.height)
        } else {
          ctx.beginPath()
          ctx.arc(obj.x, obj.y, obj.radius, 0, Math.PI * 2)
          ctx.fill()
          ctx.stroke()
        }
        
        ctx.fillText(obj.id, obj.x, obj.y - 5)
      })
    }
    
    function optimizedRender(ctx, objects) {
      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
      
      const colorGroups = new Map()
      
      objects.forEach(obj => {
        if (!colorGroups.has(obj.color)) {
          colorGroups.set(obj.color, [])
        }
        colorGroups.get(obj.color).push(obj)
      })
      
      colorGroups.forEach((objs, color) => {
        ctx.fillStyle = color
        ctx.strokeStyle = '#fff'
        ctx.lineWidth = 2
        
        objs.forEach(obj => {
          if (obj.type === 'rect') {
            ctx.fillRect(obj.x, obj.y, obj.width, obj.height)
            ctx.strokeRect(obj.x, obj.y, obj.width, obj.height)
          } else {
            ctx.beginPath()
            ctx.arc(obj.x, obj.y, obj.radius, 0, Math.PI * 2)
            ctx.fill()
            ctx.stroke()
          }
        })
      })
      
      ctx.font = '12px Arial'
      ctx.fillStyle = '#fff'
      objects.forEach(obj => {
        ctx.fillText(obj.id, obj.x, obj.y - 5)
      })
    }
    
    function generateObjects(count) {
      const objects = []
      for (let i = 0; i < count; i++) {
        const isRect = Math.random() > 0.5
        objects.push({
          id: `#${i}`,
          type: isRect ? 'rect' : 'circle',
          x: Math.random() * 350 + 25,
          y: Math.random() * 250 + 25,
          width: isRect ? 30 + Math.random() * 20 : undefined,
          height: isRect ? 30 + Math.random() * 20 : undefined,
          radius: isRect ? undefined : 15 + Math.random() * 10,
          color: colors[Math.floor(Math.random() * colors.length)]
        })
      }
      return objects
    }
    
    const unoptimizedCanvas = document.getElementById('unoptimizedCanvas')
    const optimizedCanvas = document.getElementById('optimizedCanvas')
    const unoptimizedStats = document.getElementById('unoptimizedStats')
    const optimizedStats = document.getElementById('optimizedStats')
    
    const unoptimizedCtx = unoptimizedCanvas.getContext('2d')
    const optimizedCtx = optimizedCanvas.getContext('2d')
    
    let objectCount = 5000
    let objects = generateObjects(objectCount)
    
    function runTest() {
      const start1 = performance.now()
      unoptimizedRender(unoptimizedCtx, objects)
      const time1 = performance.now() - start1
      
      const start2 = performance.now()
      optimizedRender(optimizedCtx, objects)
      const time2 = performance.now() - start2
      
      unoptimizedStats.textContent = `渲染时间: ${time1.toFixed(2)}ms`
      optimizedStats.textContent = `渲染时间: ${time2.toFixed(2)}ms`
    }
    
    function changeCount(count) {
      objectCount = count
      objects = generateObjects(count)
      runTest()
    }
    
    runTest()
  </script>
</body>
</html>

最佳实践

状态优化原则

const stateOptimizationPrinciples = {
  grouping: {
    title: '按状态分组',
    description: '将相同状态的绘制操作分组处理',
    example: `
      ctx.fillStyle = 'red'
      rects.forEach(r => ctx.fillRect(r.x, r.y, r.w, r.h))
      
      ctx.fillStyle = 'blue'
      circles.forEach(c => {
        ctx.beginPath()
        ctx.arc(c.x, c.y, c.r, 0, Math.PI * 2)
        ctx.fill()
      })
    `
  },
  
  caching: {
    title: '缓存状态',
    description: '避免重复设置相同的状态',
    example: `
      class StateCache {
        constructor(ctx) {
          this.ctx = ctx
          this.cache = {}
        }
        
        setFillStyle(color) {
          if (this.cache.fillStyle !== color) {
            this.ctx.fillStyle = color
            this.cache.fillStyle = color
          }
        }
      }
    `
  },
  
  batching: {
    title: '批量处理',
    description: '收集绘制操作后批量执行',
    example: `
      renderer.beginBatch({ fillStyle: 'red' })
      objects.forEach(obj => renderer.addRect(obj))
      renderer.endBatch()
      renderer.flush()
    `
  },
  
  pathOptimization: {
    title: '路径优化',
    description: '合并相同样式的路径',
    example: `
      ctx.beginPath()
      ctx.fillStyle = 'red'
      rects.forEach(r => {
        ctx.rect(r.x, r.y, r.w, r.h)
      })
      ctx.fill()
    `
  }
}

状态切换检查工具

class StateSwitchMonitor {
  constructor(ctx) {
    this.ctx = ctx
    this.originalMethods = {}
    this.switchCount = {}
    this.isMonitoring = false
    
    this.trackedProperties = [
      'fillStyle', 'strokeStyle', 'lineWidth', 'font',
      'globalAlpha', 'globalCompositeOperation'
    ]
  }
  
  start() {
    if (this.isMonitoring) return
    
    this.switchCount = {}
    this.trackedProperties.forEach(prop => {
      this.switchCount[prop] = 0
      
      const descriptor = Object.getOwnPropertyDescriptor(this.ctx, prop)
      if (descriptor && descriptor.set) {
        this.originalMethods[prop] = descriptor.set
        
        const monitor = this
        Object.defineProperty(this.ctx, prop, {
          get: function() {
            return descriptor.get.call(this)
          },
          set: function(value) {
            monitor.switchCount[prop]++
            descriptor.set.call(this, value)
          }
        })
      }
    })
    
    this.isMonitoring = true
  }
  
  stop() {
    if (!this.isMonitoring) return
    
    this.trackedProperties.forEach(prop => {
      if (this.originalMethods[prop]) {
        const descriptor = Object.getOwnPropertyDescriptor(this.ctx, prop)
        Object.defineProperty(this.ctx, prop, {
          set: this.originalMethods[prop]
        })
      }
    })
    
    this.isMonitoring = false
  }
  
  getReport() {
    const total = Object.values(this.switchCount).reduce((a, b) => a + b, 0)
    
    return {
      total,
      details: this.switchCount,
      recommendations: this.generateRecommendations()
    }
  }
  
  generateRecommendations() {
    const recommendations = []
    
    if (this.switchCount.fillStyle > 100) {
      recommendations.push('考虑按颜色分组渲染以减少fillStyle切换')
    }
    
    if (this.switchCount.font > 50) {
      recommendations.push('考虑预渲染文本或按字体分组')
    }
    
    if (this.switchCount.globalCompositeOperation > 20) {
      recommendations.push('考虑按合成模式分组处理')
    }
    
    return recommendations
  }
}

总结

避免频繁状态切换是Canvas性能优化的重要手段:

  1. 理解开销:了解不同状态切换的性能开销
  2. 状态管理:使用状态管理器跟踪和优化状态
  3. 分组渲染:按颜色、字体等分组批量处理
  4. 路径优化:合并相同样式的路径操作
  5. 监控分析:使用工具监控状态切换频率

通过合理管理Canvas状态,可以显著减少不必要的API调用,提升渲染性能。