Canvas文字发虚主因是坐标落半像素触发抗锯齿及DPR未对齐;需物理像素撑够、逻辑坐标取整、字体策略兜底:设canvas宽高=CSS尺寸×DPR,ctx.scale(DPR,DPR),fillText坐标Math.round(),textBaseline="middle"、textAlign="center",用完整字体声明并禁用imageSmoothing。

高清屏下 Canvas 图表文字发虚,核心不是字体选得不对,而是坐标落在半像素位置触发抗锯齿,再叠加设备像素比(DPR)未对齐导致的渲染错位。解决关键在于“物理像素撑够 + 逻辑坐标守整 + 字体策略兜底”。
统一绘图坐标系,让 fillText 坐标始终对齐像素中心
不处理 DPR 时,100px 宽的 canvas 在 2x 屏上实际只有 50 个物理像素宽,fillText(50, 30) 的 y=30 实际落在第 60 个物理像素行中间——浏览器自动插值,字就虚了。
- 先获取真实 DPR:const dpr = window.devicePixelRatio || 1
- 设 canvas 物理尺寸:canvas.width = rect.width * dpr; canvas.height = rect.height * dpr(rect 来自 getBoundingClientRect)
- 立即缩放绘图上下文:ctx.scale(dpr, dpr),此后所有 x/y 坐标仍按 CSS 像素写,逻辑不变
- 文字坐标强制取整:fillText(text, Math.round(x), Math.round(y)),尤其居中时用 Math.round(containerWidth / 2) 而非直接除
用对 textBaseline 和 textAlign,避免基线漂移
即使坐标取整,若 textBaseline 仍用默认的 "alphabetic",在高 DPR 下小字号文字的基线会因字体度量微差而视觉偏移,看起来像“浮起来”或“沉下去”。
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
- 水平居中统一设:ctx.textAlign = "center",x 传容器 CSS 宽度一半(如 300)
- 垂直居中优先用:ctx.textBaseline = "middle",y 传容器 CSS 高度一半(如 200),不依赖字体自身 ascent/descent
- 禁用动态计算 y 偏移(如用 measureText().actualBoundingBoxAscent),它返回的是未缩放的度量值,易出错
字体声明与渲染开关双加固
清晰字体本身不能解决坐标错位,但能显著提升最终呈现锐度;而某些渲染开关虽对文字间接起效,必须配对使用才不冲突。
立即学习“前端免费学习笔记(深入)”;
- 字体字符串写完整:"bold 14px 'PingFang SC', 'SF Pro Text', -apple-system, sans-serif",避免单写 "14px sans-serif" 导致回退到模糊默认字体
- 中文务必加系统级无衬线体,避开 SimSun、NSimSun 等位图/低 DPI 优化字体
- 关闭图像插值(防 drawImage 缩放文字图层时二次模糊):ctx.imageSmoothingEnabled = false
- Canvas 元素加 CSS:image-rendering: pixelated; -webkit-backface-visibility: hidden;
响应式重绘时防重复缩放和残留模糊
用户切显示器、调系统缩放、缩放网页时,DPR 可能突变。若 resize 监听里反复 ctx.scale(dpr, dpr),会叠加缩放,文字越画越小越虚。
- 每次重设 canvas 尺寸前,先重置变换:ctx.setTransform(1, 0, 0, 1, 0, 0)
- 再执行 ctx.scale(dpr, dpr),确保唯一缩放层级
- 重绘前清空:ctx.clearRect(0, 0, canvas.width, canvas.height),避免旧内容叠加新缩放
- 监听 DPR 变化用 matchMedia('(resolution)'),不只是 window.resize


















