画布样式与分辨率

深入理解Canvas分辨率概念,高DPI屏幕适配方案,包含多个交互式案例 Canvas的分辨率问题在高DPI屏幕(如Retina屏)上尤为明显。理解分辨率概念对于创建清晰的图形至关重要。

什么是分辨率

分辨率指的是图像的精细程度:

  • 绑制分辨率:画布缓冲区的像素数量(width × height)
  • 显示分辨率:CSS样式决定的显示尺寸
  • 设备像素比:物理像素与CSS像素的比率

设备像素比(DPR)

设备像素比(Device Pixel Ratio)表示一个CSS像素对应多少个物理像素:

const dpr = window.devicePixelRatio || 1
// 普通屏幕: 1
// Retina屏幕: 2
// 高端手机: 3

案例一:DPR对比效果

普通绑制 vs 高DPI适配

普通绑制(未适配高DPI)

高DPI适配

代码实现

<canvas id="normalCanvas" width="200" height="100" style="width: 200px; height: 100px;"></canvas>
<canvas id="hiDPICanvas" style="width: 200px; height: 100px;"></canvas>

<script>
const dpr = window.devicePixelRatio || 1

// 普通绑制(未适配高DPI)
const normalCanvas = document.getElementById('normalCanvas')
const normalCtx = normalCanvas.getContext('2d')

normalCtx.strokeStyle = '#3498db'
normalCtx.lineWidth = 1
normalCtx.beginPath()
normalCtx.arc(50, 50, 30, 0, Math.PI * 2)
normalCtx.stroke()

// 高DPI适配
const hiDPICanvas = document.getElementById('hiDPICanvas')
const hiDPICtx = hiDPICanvas.getContext('2d')

// 设置画布尺寸为显示尺寸的dpr倍
hiDPICanvas.width = 200 * dpr
hiDPICanvas.height = 100 * dpr

// 缩放上下文以匹配CSS尺寸
hiDPICtx.scale(dpr, dpr)

// 正常绑制(使用CSS尺寸)
hiDPICtx.strokeStyle = '#3498db'
hiDPICtx.lineWidth = 1
hiDPICtx.beginPath()
hiDPICtx.arc(50, 50, 30, 0, Math.PI * 2)
hiDPICtx.stroke()
</script>

代码解析

步骤说明
canvas.width = w * dpr画布缓冲区放大dpr倍
canvas.style.width = w + 'px'CSS显示尺寸保持不变
ctx.scale(dpr, dpr)缩放绑制上下文

案例二:高DPI适配函数

使用适配函数创建高清画布

代码实现

<canvas id="myCanvas" style="width: 400px; height: 200px;"></canvas>

<script>
function setupHiDPICanvas(canvas) {
  const dpr = window.devicePixelRatio || 1
  const rect = canvas.getBoundingClientRect()
  
  // 设置画布缓冲区尺寸
  canvas.width = rect.width * dpr
  canvas.height = rect.height * dpr
  
  const ctx = canvas.getContext('2d')
  
  // 缩放上下文
  ctx.scale(dpr, dpr)
  
  // 返回CSS尺寸供绑制使用
  return {
    width: rect.width,
    height: rect.height,
    dpr: dpr,
    ctx: ctx
  }
}

// 使用
const canvas = document.getElementById('myCanvas')
const { width, height, ctx } = setupHiDPICanvas(canvas)

// 正常绑制(使用CSS尺寸)
ctx.strokeStyle = '#3498db'
ctx.lineWidth = 2
ctx.beginPath()
ctx.arc(width / 2, height / 2, 60, 0, Math.PI * 2)
ctx.stroke()
</script>

代码解析

函数返回值说明
widthCSS显示宽度
heightCSS显示高度
dpr设备像素比
ctx已缩放的绑制上下文

案例三:响应式高DPI画布

响应式高DPI画布(调整窗口大小)

代码实现

<div id="container" style="width: 100%;">
  <canvas id="myCanvas" style="width: 100%;"></canvas>
</div>

<script>
const canvas = document.getElementById('myCanvas')
const container = document.getElementById('container')
const dpr = window.devicePixelRatio || 1

function setupCanvas() {
  const w = container.clientWidth
  const h = Math.round(w * 0.5)
  
  // 设置CSS尺寸
  canvas.style.width = w + 'px'
  canvas.style.height = h + 'px'
  
  // 设置缓冲区尺寸(高DPI)
  canvas.width = w * dpr
  canvas.height = h * dpr
  
  const ctx = canvas.getContext('2d')
  
  // 重置变换并应用DPR缩放
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0)
  
  return { w, h, ctx }
}

function draw() {
  const { w, h, ctx } = setupCanvas()
  
  // 绑制内容
  ctx.fillStyle = '#3498db'
  ctx.fillRect(w / 2 - 50, h / 2 - 25, 100, 50)
}

// 初始化
draw()

// 响应窗口大小变化
window.addEventListener('resize', draw)
</script>

代码解析

方法说明
setTransform(dpr, 0, 0, dpr, 0, 0)直接设置变换矩阵
cancelAnimationFrame()取消之前的动画帧
resize时重绘窗口变化时重新设置画布

案例四:检测设备像素比

当前设备像素信息

-
设备像素比 (DPR)
-
屏幕宽度 (CSS像素)
-
物理像素宽度
-
屏幕类型

代码实现

<script>
function getScreenInfo() {
  const dpr = window.devicePixelRatio || 1
  const screenWidth = window.screen.width
  const screenHeight = window.screen.height
  const physicalWidth = Math.round(screenWidth * dpr)
  const physicalHeight = Math.round(screenHeight * dpr)
  
  let screenType = '普通屏幕'
  if (dpr >= 3) screenType = '超高DPI'
  else if (dpr >= 2) screenType = 'Retina/高DPI'
  else if (dpr >= 1.5) screenType = '中等DPI'
  
  return {
    dpr,
    screenWidth,
    screenHeight,
    physicalWidth,
    physicalHeight,
    screenType
  }
}

const info = getScreenInfo()
console.log('设备像素比:', info.dpr)
console.log('屏幕类型:', info.screenType)
console.log('CSS像素:', info.screenWidth, 'x', info.screenHeight)
console.log('物理像素:', info.physicalWidth, 'x', info.physicalHeight)
</script>

代码解析

属性说明
window.devicePixelRatio设备像素比
window.screen.width屏幕CSS像素宽度
screenWidth * dpr物理像素宽度

高DPI适配工具函数

const CanvasUtils = {
  setupHiDPI(canvas) {
    const dpr = window.devicePixelRatio || 1
    const rect = canvas.getBoundingClientRect()
    
    canvas.width = rect.width * dpr
    canvas.height = rect.height * dpr
    
    const ctx = canvas.getContext('2d')
    ctx.scale(dpr, dpr)
    
    return {
      width: rect.width,
      height: rect.height,
      dpr,
      ctx
    }
  },
  
  clearCanvas(canvas) {
    const ctx = canvas.getContext('2d')
    const dpr = window.devicePixelRatio || 1
    ctx.setTransform(1, 0, 0, 1, 0, 0)
    ctx.clearRect(0, 0, canvas.width, canvas.height)
    ctx.scale(dpr, dpr)
  },
  
  getCanvasSize(canvas) {
    const rect = canvas.getBoundingClientRect()
    return {
      width: rect.width,
      height: rect.height
    }
  }
}

小结

Canvas分辨率要点:

  • 理解绑制分辨率与显示分辨率的区别
  • 高DPI屏幕需要放大画布缓冲区
  • 使用ctx.scale(dpr, dpr)简化绑制
  • 响应式画布需要同时处理resize和DPR
  • 检测DPR使用window.devicePixelRatio