canvas.width/height设绘图缓冲区分辨率,style.width/height仅控制CSS显示尺寸;二者不一致会导致缩放失真;正确做法是按devicePixelRatio动态设置缓冲区并用CSS匹配显示尺寸。

canvas.width 和 canvas.height 是绘图缓冲区分辨率,不是 CSS 尺寸
直接在 HTML 里写 width="800" 或用 JS 赋值 canvas.width = 800,设置的是「绘图上下文的逻辑像素总数」;而 style.width = "800px" 只是告诉浏览器“把这个画布框拉到 800 像素宽”,不改变内部栅格。两者不一致时,浏览器会缩放整个位图——线条变粗、文字发虚、getImageData 返回的像素数对不上。
常见错误现象:
-
ctx.lineWidth = 1画出来像 2 像素宽 - 导出 PNG 时图像模糊或尺寸不对
- 响应式容器里 canvas 内容被压缩/拉伸变形
为什么不能只用 CSS 设置 canvas 尺寸
CSS 的 width 和 height 对 canvas 来说只是“显示层缩放”,不会重置绘图坐标系原点或改变 ctx 的像素密度。比如你写:
<canvas id="c" style="width: 100%; height: 400px"></canvas>
但没设 HTML 属性或 JS 赋值 width/height,那 canvas 实际缓冲区还是默认的 300×150,浏览器硬把它撑成满屏宽——结果就是所有绘制内容被拉伸失真。
立即学习“前端免费学习笔记(深入)”;
正确做法始终是:
把任意文章或文档内容转换成精美的全屏翻页 HTML 演示文件(幻灯片风格)。当用户发来一篇文章、文档链接、Markdown 内容、或粘贴的文本,并且希望生成演示用的 HTML 时触发。支持任意内容来源:iWiki 链接、KM 链接、本地文件路径、直接粘贴的文本。生成的 HTML 文件保存到本地工作区,并通过企微直接发送给用户。触发词示例:帮我把这篇文章做成PPT、转成演示文档、生成HTML幻灯片、做个演示、转成可以演示的格式、article to deck、make a slide。
- 先确定目标显示尺寸(如父容器
clientWidth) - 乘以
window.devicePixelRatio得到高分屏所需缓冲区大小 - 赋给
canvas.width和canvas.height - 再用 CSS 把 canvas 显示尺寸设为对应 CSS 像素(例如
style.width = "100%")
canvas 绘图坐标系原点在左上角,缩放/平移会改变它
初始状态下,(0, 0) 是 canvas 左上角顶点,x 向右递增,y 向下递增。这个坐标系绑定在 canvas 缓冲区像素网格上,不受 CSS 缩放影响。
但一旦调用 ctx.scale(2, 2) 或 ctx.translate(50, 50),后续所有绘制命令都基于新变换后的坐标系执行。容易忽略的点:
-
ctx.scale(dpr, dpr)必须在重设width/height后立即调用,否则坐标映射错位 -
ctx.setTransform(1, 0, 0, 1, 0, 0)可重置所有变换,比多次save()/restore()更轻量 - 使用
getBoundingClientRect()获取鼠标位置时,需手动减去 canvas 左上角偏移再除以 DPR,才能对齐绘图坐标
响应式重绘必须同步更新 width/height + 重置 ctx 变换
监听 ResizeObserver 是目前最稳妥的方式。每次回调中不能只改 CSS 尺寸,必须:
- 读取父容器当前
clientWidth/clientHeight - 计算目标缓冲区:
canvas.width = Math.round(width * devicePixelRatio) - 同理设置
canvas.height - 调用
ctx.scale(devicePixelRatio, devicePixelRatio) - 最后清空并重绘(注意:改
width或height属性会自动清空画布)
漏掉 ctx.scale 这一步,高清屏上所有 fillRect、strokeText 都会变细一半;漏掉重绘,画布看起来是空的——因为缓冲区重置了,但没画新内容。
devicePixelRatio 不是静态值,iOS Safari 在横竖屏切换时可能变化,别缓存它。


















