理解Canvas的样式和分辨率的关系,是解决模糊问题的关键。
Canvas有两个尺寸概念:
分辨率:画布绑制的像素数量,由width/height属性决定。
显示大小:画布在页面上显示的尺寸,由CSS决定。
<!-- 分辨率:800x600 -->
<!-- 显示大小:400x300 -->
<canvas width="800" height="600" style="width: 400px; height: 300px;"></canvas>
<!-- 分辨率:400x300 -->
<!-- 显示大小:800x600 -->
<canvas width="400" height="300" style="width: 800px; height: 600px;"></canvas>
结果:400个像素拉伸到800个像素,每个像素放大2倍,产生模糊。
<canvas width="400" height="300"></canvas>
在2倍高清屏上:
结果: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) // 清晰
更高的分辨率意味着更多的像素需要处理:
| 分辨率 | 像素数量 | 内存占用 |
|---|---|---|
| 400x300 | 120,000 | ~0.46MB |
| 800x600 | 480,000 | ~1.83MB |
| 1920x1080 | 2,073,600 | ~7.91MB |
内存计算:像素数量 × 4(RGBA四个通道)
// 不必要的超高分辨率
const dpr = window.devicePixelRatio || 1
const maxDpr = 2 // 限制最大像素比
canvas.width = width * Math.min(dpr, maxDpr)
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)
// 绑制图表...
}
}