Vant Signature 签名组件实战指南:基于 Canvas 的手写签名、撤销与导出
【免费下载链接】vantA lightweight, customizable Vue UI library for mobile web apps.项目地址: https://gitcode.com/GitHub_Trending/va/vant
导读
Signature是 Vant 移动端组件库中用于电子签名场景的组件,底层基于 HTML5 Canvas 实现,支持手写笔迹、实时导出 base64 图片、多步撤销以及主题定制。本指南以 packages/vant/src/signature/README.md 为主体,结合组件源码、样式文件与测试用例,系统讲解组件的安装引入、基础用法、全部 Props/Events/Methods/Slots 参数、类型定义与 CSS 变量主题定制,并深入到 Canvas 绘制、撤销历史栈等底层实现原理,帮助你完整掌握在 Vue 3 移动端项目中落地签名能力的方案。
适用前提:本组件要求
vant版本 >= 4.3.0,且基于 Vue 3 + Vite 等现代构建环境。
组件概述与适用场景
Signature是专为签名场景设计的组件,核心特点:
- 基于 Canvas 实现:所有笔迹绘制都发生在
<canvas>上,不依赖第三方绘图库,组件体积小; - 开箱即用的操作栏:自带「清空 / 撤销 / 确认」三个按钮(内部复用 Vant 的
Button组件); - 数据出口统一:点击确认后,通过
submit事件一次性输出{ image, canvas },方便后续上传、存储或与后端签署流程对接; - 历史记录管理:内置撤销历史栈,支持最多
history-size步的撤销操作。
从源码结构看(Signature.tsx),组件由签名画布区(__content)和操作按钮区(__footer)两部分渲染组成,非常适合合同签署、确认书、收货签收、登记表确认等移动端表单场景。
安装与引入
通过 npm 安装
npm i vant注册组件
支持全局注册与按需引入两种方式。全局注册需要先通过createApp创建应用实例:
import { createApp } from 'vue'; import { Signature } from 'vant'; const app = createApp(); app.use(Signature);在模板中直接以<van-signature>使用:
<van-signature />从源码看,组件通过withInstall包装导出(index.ts),同时该文件为 Vue 的GlobalComponents声明了Signature全局组件类型,因此在 TypeScript + Volar 环境下使用<van-signature>时可获得完整的类型提示。更多注册方式(如按需引入、unplugin-vue-components自动导入)可参考 组件注册文档。
基础用法
核心事件流:submit 与 clear
组件的事件模型非常简单清晰。当用户点击确认按钮时,组件触发submit事件,回调的第一个参数data包含两个字段:
| 字段 | 类型 | 说明 |
|---|---|---|
image | string | 签名对应的图片,base64 字符串格式;若签名为空,则返回空字符串 |
canvas | HTMLCanvasElement | 当前绘制用的 Canvas 元素 |
当点击清空按钮时,组件触发clear事件(无参数)。
基础用法示例:
<van-signature @submit="onSubmit" @clear="onClear" /> <van-image v-if="image" :src="image" />import { ref } from 'vue'; import { showToast } from 'vant'; export default { setup() { const image = ref(''); const onSubmit = (data) => { image.value = data.image; }; const onClear = () => showToast('clear'); return { image, onSubmit, onClear, }; }, };拿到data.image后,既可以直接赋值给 Vant 的Image组件预览,也可以提交给后端保存。上述完整写法与仓库内 demo/index.vue 中的基础用法示例一致。
空签名处理
值得注意的一个细节:当画布为空时点击确认,submit事件中的image字段是空字符串而非data:image/png;base64,...形式的空图。这一点在实际业务中非常实用——你不需要额外校验签名是否为空。
从源码看(Signature.tsx),组件通过isCanvasEmpty方法创建一张与当前画布同尺寸的空白对照 Canvas(若设置了background-color,则用同色填充后对比),再通过canvas.toDataURL()与当前画布数据比对,完全一致即判定为空。这也意味着纯色背景填充不会影响空签名判定。
自定义笔触与背景
笔触颜色(pen-color)
通过pen-color属性设置笔迹颜色,默认黑色:
<van-signature pen-color="#ff0000" @submit="onSubmit" @clear="onClear" />线条宽度(line-width)
通过line-width属性设置线条宽度(数值类型),默认3:
<van-signature :line-width="6" @submit="onSubmit" @clear="onClear" />背景颜色(background-color)
通过background-color属性设置签名区背景色,默认透明(无背景色):
<van-signature background-color="#eee" @submit="onSubmit" @clear="onClear" />源码实现上,背景色通过fillRect在整个画布上填充(Signature.tsx),并在初始化、清空、撤销回退时都会被重新填充,保证任意操作后背景色保持一致。
导出图片类型(type)
type属性控制导出图片的 MIME 类型,默认png。源码中对jpg与jpeg做了特殊处理,使用canvas.toDataURL('image/jpeg', 0.8)以 80% 质量压缩导出,其余类型(如webp)则走通用分支canvas.toDataURL('image/${type}')(Signature.tsx)。需要更小体积的图片时(如用于上传),可以指定type="jpg"。
Props 完整参数表
以下为组件全部 Props(对应 Signature.tsx 中signatureProps的定义):
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| type | 导出图片类型 | string | png |
| pen-color | 笔触颜色,默认黑色 | string | #000 |
| line-width | 线条宽度 | number | 3 |
| history-size | 撤销历史记录最大数量 | number | 20 |
| background-color | 背景颜色 | string | - |
| tips | 当不支持 Canvas 时出现的提示文案 | string | - |
| clear-button-text | 清空按钮文案 | string | 清空(英文Clear) |
| undo-button-text | 撤销按钮文案 | string | 撤销(英文Undo) |
| confirm-button-text | 确认按钮文案 | string | 确认(英文Confirm) |
补充说明:
pen-color、line-width等属性在每次笔画开始时(touchStart)被重新写入 Canvas 2D 上下文(strokeStyle、lineWidth),所以运行中动态修改也会在下一笔生效;- 三个按钮文案属性均为
String类型,默认值为空时组件会回退到内置多语言文案(源码中为props.clearButtonText || t('clear')形式),即跟随组件库语言包; history-size控制撤销历史栈的最大容量,超出时最早的记录会被丢弃(详见下文「撤销实现原理」);tips用于 Canvas 不可用的兜底提示。源码通过hasCanvasSupport检测document.createElement('canvas').getContext('2d')是否存在(Signature.tsx);检测失败时渲染区会替换为提示文本(tips属性或tips插槽),而不是报错。
Events 事件
| 事件名 | 说明 | 回调参数 |
|---|---|---|
| start | 开始签名时触发(手指/触摸落下) | - |
| end | 结束签名时触发(手指抬起) | - |
| signing | 签名过程中触发 | event: TouchEvent |
| submit | 点击确认按钮时触发 | data: { image: string; canvas: HTMLCanvasElement } |
| clear | 点击清空按钮时触发 | - |
源码将事件绑定在 Canvas 的touchstart/touchmove/touchend上(Signature.tsx),并透传了原生TouchEvent,因此signing事件可用于实时获取触点坐标等场景。绘制过程中调用了preventDefault阻止默认行为(如页面滚动跟随),保证书写体验。
Slots 插槽
| 名称 | 说明 | 插槽参数 |
|---|---|---|
| tips | 自定义提示文案(Canvas 不可用时的兜底展示) | - |
当浏览器不支持 Canvas 时,若提供了tips插槽则渲染插槽内容,否则渲染tips属性文本。测试用例should render tips correctly(test/index.spec.ts)通过 mockdocument.createElement验证了该兜底分支的渲染结果。
实例方法(Methods)
通过ref可以获取 Signature 实例并调用实例方法(组件通过useExpose将方法挂载到组件实例代理上,use-expose.ts):
| 方法名 | 说明 | 参数 | 返回值 |
|---|---|---|---|
resizev4.7.3 | 外层元素大小或组件显示状态变化时调用,触发重绘 | - | - |
clearv4.8.6 | 清除签名 | - | - |
submitv4.8.6 | 触发submit事件,与点击确认按钮效果等价 | - | - |
| undo | 撤销上一次笔画 | - | - |
典型用法:在表单提交前主动调用submit获取签名数据,无需用户手动点击确认按钮:
<van-signature ref="signatureRef" @submit="onSubmit" />import { ref } from 'vue'; const signatureRef = ref(); // 需要提交时 signatureRef.value?.submit();关于resize:源码通过watch(windowWidth, resize)监听窗口宽度变化自动触发重绘(Signature.tsx),同时在初始化时根据devicePixelRatio对画布做高清屏适配(canvas.width = offsetWidth * dpr并ctx.scale(dpr, dpr),见 Signature.tsx)。当容器尺寸因布局变化而改变(如从隐藏切换为显示、旋转屏幕)时,可手动调用resize()保留原有笔迹并重绘。测试用例should call resize when window width changes(test/index.spec.ts)验证了窗口 resize 时会触发getImageData重绘逻辑。
类型定义
组件导出以下类型定义:
import type { SignatureProps, SignatureInstance } from 'vant';SignatureProps:Props 对应的类型(由ExtractPropTypes<typeof signatureProps>推导,Signature.tsx);SignatureInstance:组件实例类型,包含resize、clear、submit、undo四个公开方法(types.ts)。
SignatureInstance用法示例:
import { ref } from 'vue'; import type { SignatureInstance } from 'vant'; const signatureRef = ref<SignatureInstance>(); signatureRef.value?.resize();此外还导出了SignatureThemeVars类型(signaturePadding、signatureContentHeight、signatureContentBackground、signatureContentBorder),用于类型安全的主题变量定制(types.ts)。
撤销与历史栈的底层原理
undo与history-size的实现是 Signature 组件最具技术含量的部分,值得深入理解:
- 入栈时机:每次
touchend(一笔结束)时调用saveState,将当前画布内容通过getImageData(0, 0, canvasWidth, canvasHeight)快照存入history数组(Signature.tsx); - 容量上限:
history长度达到history-size时,最早的历史快照通过shift()丢弃,即只保留最近 N 步; - 撤销动作:
undo弹出栈顶快照后,先clearRect清空画布并重新填充背景色,再将栈顶(上一笔结束时的状态)通过putImageData恢复(Signature.tsx);当历史耗尽时,画布被完全清空; - 清空重置:
clear在清空画布的同时也会将整个history数组重置为空。
测试用例完整验证了这一机制:
undo should restore canvas to previous state(test/index.spec.ts)绘制两笔后撤销一次,断言putImageData被调用恢复第一笔状态;再撤销一次则仅执行clearRect(历史耗尽);history should be limited by historySize prop(test/index.spec.ts)在historySize: 3下绘制 5 笔,撤销 3 次后第 4 次撤销不再触发putImageData,精确验证了容量上限与丢弃策略。
这些测试同时也是理解组件行为契约的最佳参考:支持的撤销步数 =min(已绘制笔画数, history-size)。
主题定制(CSS 变量)
组件提供以下 CSS 变量用于自定义样式,推荐通过 ConfigProvider 组件 进行全局或局部主题定制:
| 名称 | 默认值 | 描述 |
|---|---|---|
| --van-signature-padding | var(--van-padding-xs) | 组件内边距 |
| --van-signature-content-height | 200px | 画布高度 |
| --van-signature-content-background | var(--van-background-2) | 画布背景色 |
| --van-signature-content-border | 1px dotted #dadada | 画布边框样式 |
这些变量的定义与使用位置见 index.less:
- 变量在
:root, :host作用域声明,便于在自定义元素/全局环境生效; - 签名区域
.van-signature__content使用flex居中布局,画布canvas的宽高被设置为100%以填满内容区,因此修改--van-signature-content-height即可直观改变书写区域高度; - 内容区同时带
border-radius: var(--van-radius-lg)圆角与overflow: hidden,保证背景填充不溢出圆角; - 底部操作栏
.van-signature__footer右对齐排布,按钮间通过margin-left分隔。
例如,想将签名区域高度调整为 300px 并改为实线边框:
// 通过 ConfigProvider 局部覆盖 import { ref } from 'vue'; const themeVars = ref({ signatureContentHeight: '300px', signatureContentBorder: '1px solid #666', });<van-config-provider :theme-vars="themeVars"> <van-signature /> </van-config-provider>完整实战示例:签署确认表单
结合以上内容,给出一个贴近真实业务(如收货签收)的完整示例:用户签名后立即预览,同时自动提交给后端:
<template> <van-config-provider :theme-vars="themeVars"> <van-signature ref="signatureRef" pen-color="#333" :line-width="4" background-color="#fff" :history-size="10" confirm-button-text="提交签名" @submit="onSubmit" @clear="onClear" /> </van-config-provider> <van-image v-if="preview" :src="preview" width="100%" /> </template> <script setup> import { ref } from 'vue'; import { showToast } from 'vant'; import type { SignatureInstance } from 'vant'; const signatureRef = ref<SignatureInstance>(); const preview = ref(''); const themeVars = { signatureContentHeight: '240px', }; const onSubmit = ({ image, canvas }) => { if (!image) { showToast('请先签名'); return; } preview.value = image; // 此处可将 base64 图片上传到服务端 console.log('canvas:', canvas); }; const onClear = () => { preview.value = ''; showToast('已清空'); }; </script>在该示例中:空签名提交会被!image拦截;submit事件同时携带了 base64 图片与 Canvas 元素,便于需要原始 Canvas 做进一步处理(如追加水印)的场景;resize()可在容器尺寸变化时调用以保持画布内容。
总结
Vant 的Signature组件用约两百行核心代码(Signature.tsx)完整实现了移动端签名场景的关键能力:Canvas 高清屏适配与绘制、空签名智能判定、多步撤销历史栈、按钮文案本地化与 CSS 变量主题定制。接入时只需关注三件事:通过submit事件接收{ image, canvas }数据、用history-size控制撤销深度、必要时通过resize()响应容器尺寸变化。相关演示与测试分别位于 demo/index.vue 与 test/index.spec.ts,可作为二次开发的直接参考。
【免费下载链接】vantA lightweight, customizable Vue UI library for mobile web apps.项目地址: https://gitcode.com/GitHub_Trending/va/vant
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考