canvas.width和style.width同时存在会强制等比缩放:浏览器以html属性为基准,按width/height比例统一缩放,导致变形、坐标错乱;正确做法是放弃混合写法,动态设置canvas.width/height并配合ctx.scale(dpr,dpr)适配高dpi。

canvas.width 和 style.width 同时存在会强制等比缩放
浏览器看到 width 和 height HTML 属性,再看到 style.width/style.height,不会按 CSS 值拉伸画布,而是用两者比例算出一个统一缩放系数——比如 width="100" height="50" + style="width: 300px; height: 200px",宽度放大 3 倍,高度就也按 3 倍(即 150px)撑开,最终渲染尺寸变成 300×150,画布被横向拉宽、纵向压扁。
典型后果包括:ctx.arc() 画不出正圆、isPointInPath() 判断失效、鼠标点击坐标映射错乱。
- 只保留 HTML
width/height,CSS 仅用于定位(如margin、position),适合固定尺寸场景 - 若必须响应式,就彻底放弃 HTML 属性,改用 JS 动态赋值
canvas.width和canvas.height,再配ctx.scale(dpr, dpr) - 绝对不要混合写法:比如设了
width="400"又在 CSS 里写width: 100%—— 这种组合最易触发不可预测的缩放
高 DPI 屏上模糊,本质是缓冲区像素不够
canvas.width/canvas.height 决定的是绘图缓冲区的实际物理像素数。如果只按 CSS 尺寸设(比如 canvas.style.width = "400px"),却没乘 window.devicePixelRatio,那在 MacBook 或 Windows 缩放 125% 下,画布实际只有 400×300 物理像素,却被拉伸到 800×600 物理像素区域显示,必然模糊。
- 正确做法:先用
canvas.getBoundingClientRect()获取真实显示宽高(别用offsetWidth,它受父容器 transform 影响) - 然后设
canvas.width = rect.width * dpr、canvas.height = rect.height * dpr,其中dpr = window.devicePixelRatio || 1 - 最后用 CSS 把显示尺寸“压回去”:
canvas.style.width = rect.width + "px"、canvas.style.height = rect.height + "px" - 记得调
ctx.scale(dpr, dpr),否则所有坐标都要手动 ×dpr,极易出错
多个 canvas 元素尺寸同步容易返回 0
用 jQuery 的 .attr("width") 读取 HTML 属性,在某些环境(比如未初始化或动态插入后未重绘)可能返回空字符串或 undefined,导致 canvas.style.width 被设成 "NaNpx" 或直接失败。
- 一律改用原生属性访问:
const w = canvas.width(注意这是 number 类型,不是字符串) - 遍历每个 canvas 时独立计算,避免共用变量覆盖尺寸
- resize 事件中先清空画布再重设尺寸:
canvas.width = w本身就会清空,不用额外调ctx.clearRect() - 如果 canvas 是懒加载或动态插入的,确保 DOM 已挂载、样式已计算完成后再读取
getBoundingClientRect()
叠加 DOM 元素(如 tooltip)错位怎么修
Canvas 上层盖了 div 或 SVG,但鼠标悬停位置和热区总差几像素——这不是 z-index 问题,而是 Canvas 内部坐标系和 CSS 像素不一致。Canvas 按物理像素渲染,DOM 按 CSS 像素定位,二者没对齐。
- 必须用
canvas.getBoundingClientRect()算真实显示区域,再结合clientX/clientY换算坐标 - 叠加的 DOM 要加
transform: translateZ(0)强制 GPU 层,否则可能被 Canvas 合成层遮挡 - Canvas 自身设
pointer-events: none,把鼠标事件透给下层 SVG 或按钮,避免拦截 - 如果用了
ctx.scale(dpr, dpr),换算鼠标坐标时也要除以 dpr,否则点不中路径
实际项目里最常被忽略的,是 getBoundingClientRect() 和 offsetWidth 的差异,以及 canvas.width = ... 那一瞬间清空画布的行为——你以为只是改尺寸,其实整张图都丢了,得立刻重绘。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











