canvas.width/height 控制位图缓冲区大小,决定绘图精度与坐标映射;仅用CSS缩放会导致插值模糊,响应式需结合ResizeObserver和devicePixelRatio动态重设并调用ctx.scale()。

canvas.width/height 属性控制的是位图缓冲区大小
HTML 的 width 和 height 属性不是“样式”,而是直接定义 canvas 内部绘图缓冲区的像素数量。比如 ,就表示浏览器要分配一块 800×600 像素的内存区域用于绘图;这个尺寸决定了所有坐标的映射关系、线条粗细、文字清晰度和 getImageData() 返回的数据量。
常见错误现象:ctx.lineWidth = 1 画出来像 2 像素宽;圆形边缘发虚;isPointInPath() 判断失效;导出 PNG 尺寸不对。
- 如果只写
style="width: 800px; height: 600px",而没设 HTML 属性或 JS 赋值,canvas 实际缓冲区仍是默认的 300×150,内容被强行拉伸 - 用 JS 动态设置时,必须赋值给
canvas.width和canvas.height,而不是canvas.style.width -
canvas.width赋值会清空当前画布(重置缓冲区),这是预期行为,不是 bug
为什么仅用 CSS 设置 canvas 尺寸必然模糊
CSS 的 width 和 height 只是告诉浏览器“把这个元素框撑到多大”,不改变内部位图分辨率。浏览器只能对现有像素做插值缩放——就像把一张 300×150 的老照片拉满全屏,再锐化也救不回细节。
在高 DPI 设备上问题更致命:若 window.devicePixelRatio === 2,而你只设了 style="width: 400px; height: 300px",那实际物理像素只有 200×150,却要填满 400×300 的显示区域,浏览器双线性插值一上,线条毛边、文字锯齿、圆形变椭圆全来了。
- 模糊不报错、不抛异常,纯视觉降级,极难定位
- 用
getBoundingClientRect().width读的是 CSS 像素,不能反推缓冲区大小 - 禁用所有 CSS 后对比渲染效果,是快速确认是否为缩放导致的最简单办法
响应式 canvas 必须监听 resize 并动态重设缓冲区
响应式容器里,canvas 的显示尺寸会随窗口或父容器变化,但缓冲区不会自动同步。必须手动监听尺寸变更,重新计算并赋值 canvas.width/canvas.height,再触发重绘。
推荐优先使用 ResizeObserver,它比 window.addEventListener('resize', ...) 更精准、无抖动、不误触:
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
const ro = new ResizeObserver(entries => {
const { width, height } = entries[0].contentRect;
const dpr = window.devicePixelRatio || 1;
canvas.width = Math.round(width * dpr);
canvas.height = Math.round(height * dpr);
ctx.scale(dpr, dpr); // 让绘图坐标回归 CSS 像素逻辑
redraw(); // 你的重绘函数
});
ro.observe(canvas.parentElement);
- 别用
clientWidth/clientHeight——它们可能受滚动条、边框影响,contentRect更可靠 -
Math.round()防止非整数值导致亚像素渲染模糊 - 每次重设
width/height后,ctx的变换矩阵会被重置,scale()必须重调
devicePixelRatio 不处理 = 高清屏上默认模糊
现代设备(MacBook Retina、iPhone、高端安卓)的 window.devicePixelRatio 普遍 ≥2。若忽略它,等于主动放弃一半以上用户的清晰度。核心等式只有一个:canvas.width = 显示宽度 × dpr,且必须在初始化和每次 resize 后执行。
验证是否生效:用 ctx.fillStyle = 'red'; ctx.fillRect(0, 0, 1, 1); 画单像素块,再用 ctx.getImageData(0, 0, 1, 1).data 读取——模糊时返回的可能是 [127, 0, 0, 255] 这类插值色,而非 [255, 0, 0, 255]。
- 不要硬编码
dpr = 2,不同设备差异大(iPad Pro 是 2,部分安卓是 3) - 服务端渲染或 SSR 场景下,
devicePixelRatio不可用,需靠客户端 hydration 补全 -
ctx.scale(dpr, dpr)是关键一步:它让开发者继续用 CSS 像素思维写坐标(比如ctx.fillRect(10, 10, 20, 20)),而底层自动映射到高分屏物理像素
实际项目中最容易被跳过的,是重设 width/height 后忘记调用 ctx.scale()。这时候你会看到 canvas 清晰了,但所有图形突然缩小成四分之一——因为绘图上下文还在用原始缓冲区坐标系,没对齐新的物理像素密度。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











