在使用 UniApp 的 Canvas 绘制水印时,常出现水印图片模糊的问题,尤其是在高清屏幕或 Retina 屏幕上更为明显。主要原因是未适配设备像素比(devicePixelRatio),导致绘制的 canvas 画布分辨率与实际显示分辨率不匹配。即使绘制了清晰的文字或图像,在高倍屏下仍会被拉伸模糊。此外,drawImage 绘制图片时若源尺寸过小或未按比例放大,也会加剧模糊现象。如何根据 devicePixelRatio 动态调整 canvas 宽高并保持绘制内容清晰,是解决该问题的关键技术难点。
1条回答 默认 最新
白萝卜道士 2025-11-28 08:34关注一、问题背景与现象分析
在使用 UniApp 的 Canvas 绘制水印时,开发者普遍反馈水印图像模糊,尤其在高清屏幕(如 Retina 屏幕)上表现尤为明显。这种模糊并非来源于原始图片质量差,而是由于未正确处理设备像素比(
devicePixelRatio),导致绘制的 canvas 实际渲染分辨率低于物理显示分辨率。现代移动设备广泛采用高 DPI 显示屏,其
window.devicePixelRatio值通常为 2 或 3,意味着每 CSS 像素对应 2×2 或 3×3 的物理像素。若 canvas 的绘制上下文未按此比例放大,绘制内容会被浏览器自动拉伸,造成失真和模糊。二、核心原理:Canvas 渲染机制与 devicePixelRatio 关系
Canvas 具有两个关键尺寸属性:
- canvas.width / canvas.height:画布的内在像素尺寸(即绘图缓冲区大小)
- CSS 宽高:控制 canvas 在页面中的显示尺寸
当二者不匹配时,浏览器会进行缩放渲染。例如,一个 200×100 的 canvas 被设置为 100×50 的 CSS 尺寸,则图像被压缩;反之则被拉伸模糊。
因此,清晰绘制的关键在于:
- 获取当前设备的
devicePixelRatio - 将 canvas 的
width和height按 ratio 放大 - 使用 CSS 将其显示尺寸还原至预期布局大小
- 在绘制时,所有坐标和尺寸也需同比例放大
三、常见错误实践对比表
实践方式 是否适配 dpr 结果 适用场景 直接设置 canvas width=200, height=100 否 模糊 仅普通屏 通过 CSS 放大 canvas 否 严重模糊 不推荐 动态设置 width = cssWidth * dpr 是 清晰 通用方案 drawImage 使用小图未放大 部分 边缘模糊 需优化源图 文本绘制未调整 fontSize 部分 字体发虚 需同步缩放 四、解决方案:基于 devicePixelRatio 的动态适配流程
function createWatermark(canvasId, text, options = {}) { const ctx = uni.createCanvasContext(canvasId); const dpr = uni.getSystemInfoSync().pixelRatio; // 获取 canvas 节点信息(异步) return new Promise((resolve, reject) => { uni.createSelectorQuery() .select('#' + canvasId) .boundingClientRect() .exec((res) => { if (!res || !res[0]) return reject('Canvas not found'); const rect = res[0]; const width = rect.width; const height = rect.height; // 动态设置 canvas 内部分辨率 const targetWidth = width * dpr; const targetHeight = height * dpr; // 更新 canvas 实际尺寸 const query = uni.createSelectorQuery(); query.select('#' + canvasId).node().exec((nodeRes) => { const canvas = nodeRes[0].node; canvas.width = targetWidth; canvas.height = targetHeight; const ctx = uni.createCanvasContext(canvasId); // 设置缩放,使绘制逻辑仍按 CSS 坐标进行 ctx.scale(dpr, dpr); // 绘制水印文字 ctx.setFontSize(options.fontSize || 16); ctx.setFillStyle(options.color || 'rgba(0,0,0,0.2)'); ctx.setTextAlign('center'); ctx.fillText(text, width / 2, height / 2); ctx.draw(false, () => { resolve({ path: canvasId, width: targetWidth, height: targetHeight, dpr }); }); }); }); }); }五、Mermaid 流程图:水印绘制适配逻辑
graph TD A[初始化 Canvas] --> B{获取设备 pixelRatio} B --> C[获取 Canvas DOM 尺寸] C --> D[计算实际绘制宽高 = CSS宽高 × dpr] D --> E[设置 canvas.width/height = 实际宽高] E --> F[调用 ctx.scale(dpr, dpr)] F --> G[按 CSS 坐标绘制水印内容] G --> H[执行 draw() 生成图像] H --> I[输出高清水印]六、进阶优化建议
除了基础的 dpr 适配外,还需注意以下几点以进一步提升清晰度:
- 确保水印图片资源本身具备足够分辨率,建议按最大 dpr(如 3x)准备素材
- 使用
imageSmoothingEnabled = false防止浏览器插值模糊(H5 端有效) - 避免频繁重绘,可缓存 canvas 图像路径用于复用
- 在 App 端可通过
uni.canvasToTempFilePath导出高清图 - 对文本水印,建议使用矢量绘制而非图片嵌入
- 测试多端表现差异:iOS Safari、Android Chrome、微信小程序等渲染行为略有不同
- 考虑使用 Web Workers 预生成水印图像,减少主线程压力
- 结合 CSS mask 或 background-image 方式替代实时绘制,提升性能
- 监控内存占用,避免过多 canvas 实例导致 OOM
- 利用
getImageData进行像素级校验,验证是否真正达到物理像素精度
本回答被题主选为最佳回答 , 对您是否有帮助呢?解决 无用评论 打赏 举报