Uniapp Canvas 2D手写签名组件深度实战:跨端适配与性能优化全攻略

2026-06-02 0 649

在电子合同、审批流程或笔记类应用中,手写签名是一个高频需求。Uniapp 作为跨端框架,要同时兼容微信小程序、H5 和 App 的 Canvas 实现,往往需要处理不同平台的 API 差异、像素比适配和触摸事件兼容。本文将从一个完整的签名组件案例出发,逐步解决这些难题,并给出可直接使用的代码与最佳实践。

一、需求分析与技术选型

一个合格的手写签名组件需要支持:

  • 手指/鼠标滑动绘制,笔迹流畅、无锯齿。
  • 跨平台:微信小程序使用 Canvas 2D API(新版),H5 使用标准 Canvas,App 使用 Webview Canvas。
  • 高清屏适配(Retina 屏 2x/3x 物理像素)。
  • 清空、撤销、保存为图片的功能。

在 Uniapp 中,我们可以统一使用 <canvas> 标签,并在不同平台通过条件编译调用对应的 Canvas 上下文 API。相对于社区中的签名插件,自己封装能够完全控制笔迹样式和导出逻辑,避免依赖冲突。

二、项目结构与基础组件

新建一个 Vue3 的 Uniapp 项目,在 components 目录下创建 SignaturePad.vue

<!-- components/SignaturePad.vue -->
<template>
  <view class="signature-container">
    <canvas 
      id="signCanvas" 
      class="sign-canvas" 
      :width="canvasWidth" 
      :height="canvasHeight"
      @touchstart="onTouchStart"
      @touchmove="onTouchMove"
      @touchend="onTouchEnd"
      @mousedown="onTouchStart"
      @mousemove="onTouchMove"
      @mouseup="onTouchEnd"
      disable-scroll="true"
    ></canvas>
    <view class="btn-group">
      <button @click="clearCanvas">清空</button>
      <button @click="saveImage">保存签名</button>
    </view>
  </view>
</template>

<script setup>
import { ref, onMounted, nextTick } from 'vue'

const canvasWidth = ref(640)
const canvasHeight = ref(400)
let ctx = null
let drawing = false

// 平台判断
const isMP = uni.getSystemInfoSync().platform !== 'windows' && uni.getSystemInfoSync().platform !== 'mac'

onMounted(async () => {
  await nextTick()
  initCanvas()
})

function initCanvas() {
  // #ifdef MP-WEIXIN
  // 小程序使用 Canvas 2D 新接口
  const query = uni.createSelectorQuery().in(this)
  query.select('#signCanvas').fields({ node: true, size: true }).exec((res) => {
    const canvas = res[0].node
    const dpr = uni.getSystemInfoSync().pixelRatio
    canvas.width = canvasWidth.value * dpr
    canvas.height = canvasHeight.value * dpr
    ctx = canvas.getContext('2d')
    ctx.scale(dpr, dpr)
    // 设置画布样式
    ctx.lineWidth = 3
    ctx.strokeStyle = '#000'
    ctx.lineCap = 'round'
    ctx.lineJoin = 'round'
  })
  // #endif

  // #ifndef MP-WEIXIN
  const canvas = document.getElementById('signCanvas')
  const dpr = window.devicePixelRatio || 1
  canvas.width = canvasWidth.value * dpr
  canvas.height = canvasHeight.value * dpr
  canvas.style.width = canvasWidth.value + 'px'
  canvas.style.height = canvasHeight.value + 'px'
  ctx = canvas.getContext('2d')
  ctx.scale(dpr, dpr)
  ctx.lineWidth = 3
  ctx.strokeStyle = '#000'
  ctx.lineCap = 'round'
  ctx.lineJoin = 'round'
  // #endif
}

三、触摸与绘制逻辑

为了兼容触摸和鼠标事件,我们统一使用事件处理函数,并提取坐标。

function getPoint(e) {
  // #ifdef MP-WEIXIN
  const touch = e.touches[0] || e.changedTouches[0]
  return { x: touch.x, y: touch.y }
  // #endif
  // #ifndef MP-WEIXIN
  const rect = e.target.getBoundingClientRect()
  const clientX = e.touches ? e.touches[0].clientX : e.clientX
  const clientY = e.touches ? e.touches[0].clientY : e.clientY
  return {
    x: clientX - rect.left,
    y: clientY - rect.top
  }
  // #endif
}

function onTouchStart(e) {
  drawing = true
  const point = getPoint(e)
  ctx.beginPath()
  ctx.moveTo(point.x, point.y)
}

function onTouchMove(e) {
  if (!drawing || !ctx) return
  const point = getPoint(e)
  ctx.lineTo(point.x, point.y)
  ctx.stroke()
  ctx.beginPath()
  ctx.moveTo(point.x, point.y) // 保证笔触连续
}

function onTouchEnd(e) {
  drawing = false
  ctx.closePath()
}

function clearCanvas() {
  ctx.clearRect(0, 0, canvasWidth.value, canvasHeight.value)
}

function saveImage() {
  // #ifdef MP-WEIXIN
  uni.canvasToTempFilePath({
    canvasId: 'signCanvas',
    success: (res) => {
      uni.saveImageToPhotosAlbum({
        filePath: res.tempFilePath,
        success: () => uni.showToast({ title: '保存成功' })
      })
    }
  })
  // #endif
  // #ifndef MP-WEIXIN
  const canvas = document.getElementById('signCanvas')
  const dataURL = canvas.toDataURL('image/png')
  // H5 下载或展示
  const link = document.createElement('a')
  link.download = 'signature.png'
  link.href = dataURL
  link.click()
  uni.showToast({ title: '图片已下载' })
  // #endif
}

四、跨端适配细节点

  • 高清屏适配:Canvas 的 CSS 尺寸与物理像素不一致时,必须按 devicePixelRatio 缩放画布和 context,否则签名会模糊。上文中已将 canvas 逻辑宽度设为 640/400,物理尺寸乘以 dpr。
  • 小程序 Canvas 2D:微信小程序基础库 2.9.0 起支持 Canvas 2D,需使用 query.select() 获取 node,不能直接使用 uni.createCanvasContext(那是旧版接口,性能差且不支持高清缩放)。
  • App 端:App 内跑的是 Webview,与 H5 一致,但部分旧系统可能需要开启硬件加速或调整交互。
  • 事件穿透:在小程序中,canvas 默认会拦截页面滚动,添加 disable-scroll="true" 可阻止页面跟随移动。

五、进阶优化:笔锋效果与性能

真实的签名往往有粗细变化。可以通过计算两点的移动速度动态调整 lineWidth。在 onTouchMove 中:

let lastTime = 0
let lastPoint = null

function onTouchMove(e) {
  if (!drawing || !ctx) return
  const point = getPoint(e)
  const curTime = Date.now()
  if (lastPoint) {
    const distance = Math.sqrt((point.x - lastPoint.x) ** 2 + (point.y - lastPoint.y) ** 2)
    const velocity = distance / (curTime - lastTime) // 像素/毫秒
    // 根据速度调整线宽:速度越快越细
    const lineWidth = Math.max(1, 3 - velocity * 0.8)
    ctx.lineWidth = lineWidth
  }
  ctx.lineTo(point.x, point.y)
  ctx.stroke()
  ctx.beginPath()
  ctx.moveTo(point.x, point.y)
  lastPoint = point
  lastTime = curTime
}

此外,为提高性能,应尽量减少 stroke() 调用频率。可以在 touchmove 中收集点,在 requestAnimationFrame 中批量绘制(H5/App 可用),但小程序中不支持 rAF,可以适当降低采集密度。

六、完整代码与使用

以上所有片段组合后,即可得到一个跨端手写签名组件。在页面中直接引用:

<template>
  <SignaturePad />
</template>

<script setup>
import SignaturePad from '@/components/SignaturePad.vue'
</script>

运行到微信小程序、H5 或 App,均能获得一致的签名体验。该组件还可继续封装为 uni_modules 插件,方便多处复用。

七、常见问题与解决

Q:小程序真机上 Canvas 不显示或位置偏移?

A:确保使用 Canvas 2D 方式(node 接口),并给 canvas 设置固定的宽高。偏移通常是父容器的 padding 或自身样式未重置导致,建议设置 box-sizing: border-box 并在组件内限制尺寸。

Q:H5 端签名图片保存后背景是黑色?

A:Canvas 默认透明背景转为图片时可能变黑。在初始化时先绘制白色背景:ctx.fillStyle = '#fff'; ctx.fillRect(0, 0, w, h)

八、总结

从触摸坐标的捕获、跨端 Canvas API 的对齐,到高清屏适配和笔锋优化,本文完整展示了在 Uniapp 中自建一个手写签名组件的全过程。相比直接使用第三方插件,自主封装不仅更灵活,而且能深入掌握各端 Canvas 渲染的差异。希望该案例能帮助你更自信地在 Uniapp 中处理图形交互需求。

Uniapp Canvas 2D手写签名组件深度实战:跨端适配与性能优化全攻略
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

版权声明:
本站资源有的来自互联网收集整理,本站纯免费分享提供学习使用,如果侵犯了您的合法权益,请联系本站我们会及时删除。
本站资源仅供研究、学习交流之用,免费开源项目不代表完全可商用,若商业用途请先咨询开发企业能否商用,否则产生的一切后果将由下载用户自行承担。
原创板块未经允许不得转载,否则将追究法律责任。

淘吗网 uniapp Uniapp Canvas 2D手写签名组件深度实战:跨端适配与性能优化全攻略 https://www.taomawang.com/web/uniapp/2064.html

下一篇:

已经没有下一篇了!

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务