画布样式与分辨率

理解Canvas的样式和分辨率的关系,是解决模糊问题的关键。

分辨率与显示大小

Canvas有两个尺寸概念:

分辨率:画布绑制的像素数量,由width/height属性决定。

显示大小:画布在页面上显示的尺寸,由CSS决定。

<!-- 分辨率:800x600 -->
<!-- 显示大小:400x300 -->
<canvas width="800" height="600" style="width: 400px; height: 300px;"></canvas>

为什么会模糊

情况1:分辨率小于显示大小

<!-- 分辨率:400x300 -->
<!-- 显示大小:800x600 -->
<canvas width="400" height="300" style="width: 800px; height: 600px;"></canvas>

结果:400个像素拉伸到800个像素,每个像素放大2倍,产生模糊。

情况2:高清屏

<canvas width="400" height="300"></canvas>

在2倍高清屏上:

  • CSS像素:400x300
  • 物理像素:800x600
  • 画布分辨率:400x300

结果:400个像素点显示在800个物理像素上,产生模糊。

解决模糊问题

方案:分辨率匹配物理像素

function createSharpCanvas(width, height) {
  const canvas = document.createElement('canvas')
  const dpr = window.devicePixelRatio || 1
  
  // 分辨率 = 显示大小 × 设备像素比
  canvas.width = width * dpr
  canvas.height = height * dpr
  
  // 显示大小
  canvas.style.width = width + 'px'
  canvas.style.height = height + 'px'
  
  const ctx = canvas.getContext('2d')
  
  // 缩放上下文,让绑制坐标使用CSS像素
  ctx.scale(dpr, dpr)
  
  return { canvas, ctx }
}

// 使用
const { canvas, ctx } = createSharpCanvas(400, 300)
document.body.appendChild(canvas)

// 绑制时使用CSS像素坐标
ctx.fillRect(0, 0, 100, 100)  // 清晰

分辨率对性能的影响

更高的分辨率意味着更多的像素需要处理:

分辨率像素数量内存占用
400x300120,000~0.46MB
800x600480,000~1.83MB
1920x10802,073,600~7.91MB

内存计算:像素数量 × 4(RGBA四个通道)

性能优化建议

// 不必要的超高分辨率
const dpr = window.devicePixelRatio || 1
const maxDpr = 2  // 限制最大像素比

canvas.width = width * Math.min(dpr, maxDpr)

CSS样式技巧

保持宽高比

canvas {
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
}

居中显示

canvas {
  display: block;
  margin: 0 auto;
}

响应式容器

function fitToContainer(canvas) {
  const parent = canvas.parentElement
  const width = parent.clientWidth
  const height = parent.clientHeight
  
  const dpr = window.devicePixelRatio || 1
  
  canvas.width = width * dpr
  canvas.height = height * dpr
  canvas.style.width = width + 'px'
  canvas.style.height = height + 'px'
  
  const ctx = canvas.getContext('2d')
  ctx.scale(dpr, dpr)
  
  return { width, height }
}

导出图片时的分辨率

导出图片时可以指定分辨率:

// 导出当前分辨率
const dataURL = canvas.toDataURL('image/png')

// 导出指定尺寸(需要创建临时画布)
function exportCanvas(canvas, width, height) {
  const tempCanvas = document.createElement('canvas')
  tempCanvas.width = width
  tempCanvas.height = height
  
  const tempCtx = tempCanvas.getContext('2d')
  tempCtx.drawImage(canvas, 0, 0, width, height)
  
  return tempCanvas.toDataURL('image/png')
}

// 导出高清图片
const hdImage = exportCanvas(canvas, 1920, 1080)

实际案例:高清图表

class HiDPIChart {
  constructor(container, options = {}) {
    this.canvas = document.createElement('canvas')
    container.appendChild(this.canvas)
    
    this.dpr = window.devicePixelRatio || 1
    this.options = options
    
    this.resize()
    window.addEventListener('resize', () => this.resize())
  }
  
  resize() {
    const container = this.canvas.parentElement
    const width = container.clientWidth
    const height = container.clientHeight
    
    this.canvas.width = width * this.dpr
    this.canvas.height = height * this.dpr
    this.canvas.style.width = width + 'px'
    this.canvas.style.height = height + 'px'
    
    this.ctx = this.canvas.getContext('2d')
    this.ctx.scale(this.dpr, this.dpr)
    
    this.width = width
    this.height = height
    
    this.draw()
  }
  
  draw() {
    // 使用 this.width 和 this.height(CSS像素)
    this.ctx.clearRect(0, 0, this.width, this.height)
    // 绑制图表...
  }
}