根本原因是逻辑坐标系与视觉呈现脱节:容器拉伸时 canvas 的 width/height 未同步更新,导致浏览器用 CSS 缩放强行撑开画布,使绘图操作被二次扭曲;ResizeObserver 可精准监听任意容器尺寸变化并同步物理尺寸,配合 dpr 适配和重绘即可解决。
Canvas 绘图在容器拉伸后出现比例失真,根本原因不是“画得不准”,而是逻辑坐标系与视觉呈现脱节——当容器尺寸变化(比如响应式布局、横竖屏切换、折叠屏展开),而 canvas 的 width/height 属性没同步更新时,浏览器会用 CSS 缩放强行撑开画布,导致所有 ctx.rect()、ctx.drawImage() 等操作被二次扭曲。
ResizeObserver 是目前最轻量、最精准监听容器尺寸变化的方案,它不依赖窗口 resize,能捕获任意父容器的宽高变动,特别适合嵌入 flex/grid/scroll 容器或小程序 WebView 中的 canvas。
窗口 resize 无法感知内部容器缩放(如 sidebar 展开、modal 弹出、CSS transform 变化);移动端横竖屏切换时,window.resize 触发时机不稳定,且常漏掉初始渲染后的尺寸;它也无法区分是宽度变还是高度变,而 ResizeObserver 能精确返回 contentRect,含 width、height、inlineSize、blockSize 四个维度。
核心逻辑:监听容器尺寸 → 拿到目标宽高 → 设置 canvas.width/height → 清空并重绘 → (可选)适配设备像素比(dpr)
canvas.style.width 或 style.height,只用 CSS 控制容器大小,让 canvas 填满即可(如 width: 100%; height: 100%)container.getBoundingClientRect() 或直接取 ResizeObserver 回调中的 entry.contentRect
window.devicePixelRatio,再用 ctx.scale(dpr, dpr) 对齐逻辑坐标HTML:
<div id="canvas-container" style="width: 100%; height: 400px;"><br> <canvas id="myCanvas"></canvas><br></div>
JavaScript:
const canvas = document.getElementById('myCanvas');const ctx = canvas.getContext('2d');const container = document.getElementById('canvas-container');<p>const dpr = window.devicePixelRatio || 1;</p><p>const resizeObserver = new ResizeObserver(entries => {const entry = entries[0];const { width, height } = entry.contentRect;</p><p>// 设置物理像素尺寸canvas.width = Math.round(width <em> dpr);canvas.height = Math.round(height </em> dpr);</p><p>// 重置绘制上下文缩放ctx.setTransform(dpr, 0, 0, dpr, 0, 0);</p><p>// ✅ 必须重绘redraw(ctx, width, height);});</p><p>resizeObserver.observe(container);</p><p>function redraw(ctx, cssWidth, cssHeight) {ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);</p><p>// 示例:绘制一个居中矩形(按 CSS 尺寸逻辑绘制)const x = cssWidth / 2 - 100;const y = cssHeight / 2 - 50;ctx.fillStyle = '#4a90e2';ctx.fillRect(x, y, 200, 100);}
如果 canvas 上叠加了 AI 检测框(如 face-api.js 返回的 boundingBox),这些坐标是基于原始视频帧或模型输入尺寸的,不能直接映射到 canvas 像素——必须做两层换算:
cssWidth/cssHeight 还原为实际绘制坐标(非 canvas.width/height!)getBoundingClientRect(),应在 ResizeObserver 回调中缓存最新 CSS 尺寸供绘制使用