uni.createCanvasContext 是唯一合法入口,必须用 canvas-id 字符串在 onReady 或 nextTick 后调用;签名坐标需除 pixelRatio 及动态缩放系数,且应加距离阈值过滤 touchmove。

uni.createCanvasContext 是唯一合法入口,别碰 getContext
uni-app 的 canvas 组件不是原生 DOM 元素,document.getElementById('xxx').getContext('2d') 在任何端(H5、小程序、App)都会返回 null 或直接报错。必须用 uni.createCanvasContext,且第一个参数是模板中写的 canvas-id 字符串,不是 id 或 ref。
常见错误:
-
canvas-id="signCanvas"写成了id="signCanvas"或带空格/变量(如canvas-id="{{id}}") - 在
onLoad阶段就调用uni.createCanvasContext,此时组件还没渲染,context 为空 - H5 下误用
type="2d"模式,导致 context 创建方式突变(推荐统一用默认模式)
正确做法:在 onReady 或 nextTick 后创建;确保模板中 <canvas canvas-id="signCanvas"></canvas> 存在且拼写一致。
签名坐标必须除 pixelRatio,否则真机偏移几十像素
touch 事件的 e.touches[0].x 是屏幕坐标,而 canvas 内部坐标系受设备像素比影响。不换算直接画,iOS 和安卓真机上签名会整体右下偏移,甚至断笔、点不准。
必须用:const ratio = uni.getSystemInfoSync().pixelRatio; 然后把原始坐标除以它:const x = e.touches[0].x / ratio;
额外注意:
- 如果
<canvas></canvas>设置了width/height属性(比如width="750"),但 CSS 宽高被拉伸,实际绘制区域会缩放——此时仅除pixelRatio不够,还得用getBoundingClientRect()动态算缩放系数(H5 必做) - App 端建议 canvas 固定宽高(rpx 或 px),避免百分比 + flex 导致缩放不可控
- 加距离阈值过滤高频 touchmove:两点欧氏距离
导出图片前必须先 drawCanvas,否则返回空白图
uni.canvasToTempFilePath 不会自动触发 canvas 渲染,它只读取当前 canvas 缓存帧。如果你没主动调用 ctx.draw(),哪怕界面上看起来画好了,导出结果也是透明或纯白。
关键步骤顺序不能错:
- 缓存坐标点(非实时绘图)→ 重绘函数批量画到 canvas → 调用
ctx.draw(true)(true表示同步绘制)→ 再调uni.canvasToTempFilePath -
canvasToTempFilePath的canvasId必须和模板中canvas-id完全一致 - 必须传
success回调——失败时静默返回空字符串,不报错,不提示,极易漏判 - H5 下需确保域名已加白名单,且页面为 HTTPS(否则 canvas 导出受限)
清空画布别用 clearRect,要重置点数组再重绘
ctx.clearRect(0, 0, width, height) 在多数平台(尤其 H5)不生效,或清不干净。本质是 canvas 渲染机制问题:你清的是“当前帧”,但历史点还在,下次重绘仍会叠加。
可靠做法是:
- 维护一个二维点数组
[[{x,y}], [{x,y},{x,y}]],每条笔画一个子数组 - 清空操作:置空该数组 + 调一次重绘函数(清空后重绘 = 白画布)
- 重绘函数逻辑:遍历点数组,对每条笔画
moveTo起点,逐点lineTo,最后stroke - 这样既能清空,又能保证横竖屏切换、缩放后重绘依然准确
最后提醒:签名图合并到合同图片时,别在前端 canvas 上硬叠——先分别导出签名图和合同图,再由后端用 ImageMagick 或 Canvas API 合并,避免跨域、尺寸错乱、透明通道丢失等隐性坑。


















