必须直接设置 canvas.width 和 height 属性,否则画布保持默认 300×150 像素导致绘图拉伸失真;css 仅缩放显示,不改变实际分辨率;js 修改 width/height 会清空画布并重置上下文;高 dpi 下需结合 devicepixelratio 缩放缓冲区与坐标系。

必须直接设置 canvas.width 和 canvas.height 属性,不能只靠 CSS;否则画布实际分辨率永远卡在默认的 300×150 像素,所有绘图都会被拉伸失真。
为什么 width 和 height 必须写在 HTML 标签里或用 JS 赋值
浏览器对 <canvas></canvas> 的 width 和 height 属性有硬编码默认值:300 和 150。不显式设置,就不是“没设”,而是“已设为 300×150”。这个尺寸直接决定:
- 绘图坐标系的真实范围(
ctx.fillRect(0,0,300,150)才能铺满) -
ctx.lineWidth = 1的物理粗细(DPR=2 时若不放大缓冲区,实际是 2 物理像素宽) -
getImageData()返回的data.length(= width × height × 4) - 鼠标点击映射到画布坐标的准确性(
event.clientX需除以缩放比)
只用 CSS 设置 style.width/style.height,等于让浏览器对这块 300×150 的位图做插值拉伸——就像把一张小图全屏播放,再怎么调 sharpness 也救不回边缘锯齿。
canvas.width 和 canvas.height 被 JS 修改时会发生什么
赋值 canvas.width = 800 或 canvas.height = 600 不是“缩放”,而是重建整个绘图缓冲区:
- 画布内容立即清空(所有已绘制图形消失)
- 上下文状态全部重置:变换矩阵归零、裁剪路径清除、当前字体/颜色/线宽恢复默认
- 必须在赋值后立刻重绘(调用你的
draw()函数) - 值必须是整数:写成
"800px"或800.5会被忽略,回退到 300×150
对比危险操作:canvas.style.width = "800px" —— 内容还在,但坐标系和显示尺寸错位,后续 isPointInPath() 判断会失效。
响应式 + 高 DPI 设备下的正确初始化流程
仅按 clientWidth/clientHeight 设置不够,必须乘以 window.devicePixelRatio 并同步缩放坐标系:
- 先读取布局尺寸:
const w = canvas.clientWidth; const h = canvas.clientHeight; - 再算物理像素:
const dpr = window.devicePixelRatio || 1; - 设置缓冲区:
canvas.width = Math.floor(w * dpr); canvas.height = Math.floor(h * dpr); - 保持逻辑坐标一致:
ctx.scale(dpr, dpr); - 最后控制显示尺寸:
canvas.style.width = w + "px"; canvas.style.height = h + "px";
漏掉 ctx.scale(dpr, dpr),你画的 rect(10,10,100,100) 会变成 200×200 物理像素(DPR=2 时),但坐标系仍按 1:1 解释,导致图形位置偏移、交互区域错乱。
最容易被忽略的静默陷阱
哪怕你初始 JS 设置对了,只要后续某处 CSS(比如父容器 flex 布局、全局 reset.css)悄悄改了 canvas 的 style.width/style.height,而没同步更新其 width/height 属性,失真就一直存在——它不会报错,也不会警告,直到你画一条 45° 斜线、贴一张 PNG、或者做精确的点击检测时,才突然发现圆变椭圆、文字发虚、坐标偏移几十像素。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











