根本原因是iOS WebView未对齐DPR,需同时设置canvas.width/height为逻辑尺寸×dpr、CSS宽高保持逻辑尺寸,并在绘图前调用ctx.scale(dpr,dpr),缺一不可。

为什么iOS真机Canvas总是发虚?
根本原因不是uni-app写法错,而是iOS WebView对canvas的渲染机制和DPR(设备像素比)没对齐。iPhone普遍DPR=2或3,但你写的canvas.width/canvas.height如果只按设计稿尺寸(比如375×667)设,实际绘图缓冲区只有375×667物理像素,却要撑满750×1334的屏幕区域——浏览器只能插值拉伸,结果就是文字毛边、线条糊成灰带、图片锯齿明显。
必须同时设置canvas.width/height和CSS宽高
只改style.width或只改canvas.width都不行,二者必须配对处理:
-
canvas.width和canvas.height决定绘图缓冲区分辨率(单位:物理像素),必须等于「逻辑尺寸 × dpr」 -
style.width和style.height控制显示大小(单位:CSS像素),保持设计稿尺寸即可 - 漏掉任意一项,Canvas就会被浏览器自动缩放,模糊不可避免
示例(Vue 3 setup语法):
const dpr = uni.getSystemInfoSync().pixelRatio
const logicWidth = 375
const logicHeight = 667
<p>// 获取canvas节点(注意:必须用createSelectorQuery)
uni.createSelectorQuery()
.select('#myCanvas')
.fields({ node: true, size: true })
.exec(res => {
const canvas = res[0].node
const ctx = uni.createCanvasContext('myCanvas', this)</p><pre class="brush:php;toolbar:false;">// 关键:设置物理分辨率
canvas.width = logicWidth * dpr
canvas.height = logicHeight * dpr
// 关键:CSS仍按逻辑尺寸显示
const query = uni.createSelectorQuery()
query.select('#myCanvas').fields({ size: true }).exec(sizeRes => {
const el = sizeRes[0]
// 这里可动态绑定style,或直接写死
// style="width: 375px; height: 667px"
})
// 绘图前必须scale,否则坐标全乱
ctx.scale(dpr, dpr)})
drawImage加载远程图时iOS常空白或糊?
直接传HTTPS URL给ctx.drawImage()在iOS上大概率失败,原因有两个:
- CORS限制导致图片加载失败(白板)
- iOS WebView对跨域图做降采样,原始像素被压缩(糊)
正确路径是绕过浏览器加载层:
- 先用
uni.downloadFile()下载到本地临时路径 - 再用
uni.getImageInfo()读取真实宽高(含orientation修正) - 最后用
ctx.drawImage()画本地路径,确保像素无损
注意:getImageInfo()返回的width/height是原始物理像素值,绘图时需除以dpr换算回逻辑坐标系,否则会放大错位。
type: '2d'在iOS某些基础库版本下会丢上下文
实测发现,iOS微信客户端基础库 2.29.x ~ 2.32.x 对type: '2d'支持不稳定,调用ctx.draw()后canvas.toTempFilePath()可能返回空路径,或生成图完全黑屏。
- 降级方案:改用
type: 'webgl'(兼容性更好,渲染质量也更高) - 注意:WebGL上下文不支持
fillText等2D文本API,含文字的海报得用drawImage贴字图替代 - 若必须用文字,可在
type: '2d'下加兜底重试逻辑,检测toTempFilePath返回是否为空
创建上下文时显式声明类型:
uni.createCanvasContext('myCanvas', this, { type: 'webgl' })
最易被忽略的点:每次canvas尺寸变化(比如横竖屏切换、弹窗遮挡后恢复)都得重新查pixelRatio、重设canvas.width/height、重调ctx.scale()。静态初始化只管第一次,后续尺寸变动不处理,模糊就又回来了。











