Canvas“变形”源于width/height属性与Flex布局错位:HTML中写死的width/height优先级高于JS设置,需删除;ResizeObserver应监听父元素并用contentRect获取真实尺寸,乘devicePixelRatio后取整赋值canvas.width/height,同步clearRect重绘。

Canvas在Flex容器里为什么“看起来变了形”
不是Flex搞坏了Canvas,而是Canvas的渲染机制和Flex布局天然错位:Canvas画布的像素分辨率由width和height这两个HTML属性决定,而Flex只控制CSS布局盒(即getBoundingClientRect()返回的尺寸)。只要canvas.width还是300、canvas.height还是150,哪怕Flex把它撑到1200×800的显示区域,浏览器也只能把那300×150像素强行拉伸——结果就是模糊、坐标漂移、鼠标点不准。
必须删掉HTML里的width/height属性
这是最容易被忽略的第一步。如果<canvas width="300" height="150">写死在HTML里,JS后续对canvas.width或canvas.height的赋值会被静默忽略(尤其在某些旧版Safari和IE中)。浏览器会优先采用HTML属性值,导致ResizeObserver回调里算出来的尺寸完全失效。
- 删掉所有
width和height属性,只保留<canvas> - 确保父元素(即
canvas.parentElement)有position: relative,否则getBoundingClientRect()可能相对于body计算,在折叠面板或modal中偏差极大 - Canvas自身加
display: block; width: 100%; height: 100%,仅用于CSS层面对齐显示,不替代像素级重绘
用ResizeObserver监听parentElement,不是canvas自己
ResizeObserver监听canvas自身几乎永远不触发——因为Canvas的width/height属性变化不会引起布局尺寸改变;真正变化的是它所在的Flex item(比如侧边栏收起、响应式断点切换)。所以必须观察canvas.parentElement,且回调里只信任entries[0].contentRect。
- 读取
entries[0].contentRect.width和entries[0].contentRect.height,它们是渲染后的真实CSS像素尺寸 - 高DPI设备(MacBook/iPhone)必须乘
window.devicePixelRatio,但结果要Math.floor()取整,避免小数宽高引发渲染异常 - 同步三件事:
canvas.width = Math.floor(rect.width * dpr)、canvas.height = Math.floor(rect.height * dpr)、立即ctx.clearRect(0, 0, canvas.width, canvas.height),缺一不可
别碰flex:1 + width:0这种组合
这个组合看似想“先压零再撑开”,实则致命:flex: 1不触发Canvas重绘;width: 0会让getBoundingClientRect().width返回0或NaN,JS根本拿不到有效尺寸。Canvas不是普通div,它需要明确的整数像素值才能初始化绘图缓冲区。
立即学习“前端免费学习笔记(深入)”;
- Canvas父容器的Flex子项应设为
flex: 0 0 auto,防止被flex-shrink压缩,同时保留原始尺寸作为基准 - 绝对不要用
canvas.clientWidth:它忽略transform、滚动偏移、border-box误差,数值不可靠 - 移动端绘制逻辑里统一处理DPR缩放,别在ResizeObserver里调
ctx.scale(dpr, dpr)——这会让所有坐标系偏移,鼠标位置和fillRect落点彻底错位
Canvas自适应真正的难点不在CSS布局,而在「尺寸更新」和「重绘时机」必须严格同步:读尺寸、改width/height、清空画布、重绘内容,这四步必须在同一个渲染帧内完成,漏掉clearRect或跨帧执行,旧帧残留+新坐标系错位就会立刻显现。


















