跳转到内容

SignaturePad 签名板

用指针书写的画布:按下落笔、移动成迹、抬起收笔,输出可缩放、可直接提交的 SVG。

用法

一块画布加一条笔迹路径即可:按下落笔、移动成迹、抬起收笔

组件结构

加粗的是必需部件。

data-scope="signature-pad"root · label · control · guide · path · clear-trigger · status · hidden-input

示例

标题、基准线与清空

基准线是纯画面(带 aria-hidden),清空按钮是原生 button,读屏朗读的是 translations 中的文案

请在下方签名

参与表单

提供 name 后带上表单影子,提交的是一份独立 SVG;表单重置会把画布清空

验收签名(必填)尚未签名

笔迹外形

drawing 调整笔宽与压感:thinning 越大,划得越快笔画越细,simulatePressure 决定压感取设备值还是按速度计算

只读与禁用

只读时已绘制的笔迹可见但不可修改,禁用时连清空按钮都不可按下;两者都使用原生 disabled,不只是视觉置灰

只读
禁用

取出签名

签名定稿时 draw-end 带上一份可直接入库的 SVG;提交前用 empty 拦截一次,空签名不应离开客户端

SVG 0 字节

设计指引

何时使用

  • 承诺书、回执、验收单上需要手写签名。
  • 交付确认、上门服务签收等需要留下确认痕迹的场景。

何时不用

  • 需要已有的签名图片时,属于上传,使用文件上传
  • 需要打字签名或姓名核对时,属于一行文本,使用文本字段
  • 需要在图片上圈画批注时,本组件只绘制自己的画布,不承载底图。

特性

  • 笔迹是 SVG 填充路径,放大不模糊;每一笔是同一条路径上的一条子路径。
  • 第一笔落下时测量一次画布并固定这套坐标,画布的 viewBox 与导出的 SVG 都使用它:容器变宽变窄时,已有笔迹跟随缩放而不是停留在原像素上错位。清空后重新测量。
  • drawing 一组选项调整笔画外形:size 决定粗细,thinning 让粗细随压感变化,simulatePressure 决定压感取设备值还是按落笔速度计算。
  • name 即参与表单提交,提交的是一份独立的 SVG 文档;表单重置会清空画布。
  • 笔迹变化时发出 draw,签名定稿时发出 draw-end:抬笔、点击清空、表单重置三条路径都发出。按 draw-end 缓存待提交的 SVG 不会取到过期版本。
  • 指针划出画布甚至划出窗口都持续跟随,抬起即收笔;落笔的指针被捕获,手掌与第二根手指的移动不会续进这一笔。
  • 画布是一块字段外壳:静息不填底 + --xh-border-control 描边 + 4px 控件圆角、无影;落笔时描边加深,只读只换淡底,禁用退到 --xh-border-default + --xh-bg-subtle。画布按宽高比撑高,吃不下字段家族配方钉死的控件行高,因此外壳按同一套字段规则自绘。
  • 标签走字段标签档(14 / 500 / --xh-fg-default),贴画布 --xh-space-1;状态句是说明角色(13 / --xh-fg-muted)。
  • 清空按钮走 Action Control text 档 sm:缺省 outline 描边,白底承载阶梯悬停 100 → 按下 200,按下缩放并换底;空画布时只把静息前景压淡,按钮照常可按。

组合

  • 表单字段搭配:标题、说明与错误提示交给字段,签名板只负责画布。
  • 放入表单,提供 name 后随表单提交与重置。
  • 对话框搭配做签名确认:确认按钮的可用状态读取 empty

节点形状是硬约束

画布这一族部件必须落在特定标签上,写错不报错,但无法绘制:

  • control 必须是 <svg>
  • guide 必须是 control 内的 <line>
  • path 必须是 control 内的 <path>
  • clear-trigger 必须是原生 <button>hidden-input 必须是原生 <input>

viewBox 由组件写入,作者不要在 control 上再写。

两个适配器的分工

  • Vue:XhSignaturePadRoot 的默认插槽给出 empty / paths / drawing / statusTexttoSvg() / clear();也可以用 useSignaturePad() 自行获取。XhSignaturePadGuideXhSignaturePadPath 必须写在 XhSignaturePadControl 内:SVG 命名空间由该子树带下,移出后会成为 HTML 元素,无法绘制。
  • Web Components:结构由作者编写(Light DOM,不投影插槽)。<xh-signature-pad> 上有 clear()toSvg() 与只读的 empty;提交前取签名用 toSvg(),不需要缓存上一次 draw-end
  • 两侧的 status 部件内都不需要自行写文字:节点为空时由适配器填入内建文案;写了文字则以作者的为准。

最佳实践

  • 给清空按钮一句可见文字或稳定的图标语义,不只依靠一个叉。
  • 清空之后安置焦点:按钮被禁用或收起时焦点会回到 <body>,键盘用户每清空一次就丢失一次位置。要么让按钮始终可按(本组件的默认做法),要么清空后显式把焦点交给下一个落点。
  • 提交前用 empty 拦截:空签名与潦草签名是两回事,前者应在客户端拦截。
  • 需要缓存待提交的 SVG 时按 draw-end 缓存:清空与表单重置同样会发出它,缓存不会停留在旧版本。不要嗅探清空按钮的点击。
  • 存储的是 SVG 文本,不是位图。需要位图时在服务端渲染,不在前端截屏。

反模式

  • 把画布做得很窄:手写需要面积,过窄的画布只会产生无法辨认的字迹。
  • 让签名成为唯一的确认方式却不提供替代路径,这是可达性问题。
  • 把签名图当作身份凭证。它证明的是有人在画布上书写过,不是书写者的身份。

API 参考

产物

自定义元素<xh-signature-pad>
Vue 组件XhSignaturePadClearTrigger XhSignaturePadControl XhSignaturePadGuide XhSignaturePadHiddenInput XhSignaturePadLabel XhSignaturePadPath XhSignaturePadRoot XhSignaturePadStatus
组合式函数useSignaturePad
状态机signaturePadMachine
皮肤@xihan-ui/styles/signature-pad.css

Props

属性类型必填说明
disabledboolean整块不可交互:不响应落笔,清空按钮也不可按下。
readOnlyboolean只读:已绘制的签名照常显示,但不可修改。
requiredboolean
invalidboolean校验未通过的标记,只改变外观与表单影子上的 aria-invalid。
namestring表单字段名;提供后表单影子才带 name 并参与提交。
drawingSignaturePadDrawingOptions笔迹外形。默认为 4px 恒定粗细。
translationsPartial<SignaturePadTranslations>
onDraw(details: SignaturePadDrawDetails) => void每收进一个点通知一次,清空与表单重置时也通知一次(路径为空)。
onDrawEnd(details: SignaturePadDrawEndDetails) => void签名定稿时通知一次并附带可直接提交的 SVG:抬笔、清空、表单重置三条路径都发出。

事件

自定义元素将载荷放在 detail;Vue 使用同名 emit。

事件载荷说明
drawSignaturePadDrawDetails笔迹变化时通知一次(含清空与表单重置);detail 为 { paths: string[], path: string }
draw-endSignaturePadDrawEndDetails签名定稿时通知一次(抬笔、清空、表单重置);detail 为 { paths: string[], svg: string },svg 可直接存储

插槽

仅列出带载荷的插槽。

Vue 组件插槽载荷说明
XhSignaturePadRootdefaultSignaturePadRootSlotProps

状态

以下名称仅用于内部状态机。

状态drawing · idle

事件DRAW.START · DRAW.MOVE · DRAW.END · STROKES.CLEAR · FORM.RESET · PRESS.START · PRESS.END

判据canDraw · canPress

connect API

getXxxProps() 返回对应部件的宿主属性。

成员类型说明
pathsreadonly string[]逐笔的填充轮廓 d 串,按落笔先后排列。
emptyboolean没有任何笔迹。
drawingboolean笔正落在画布上。
disabledboolean
readOnlyboolean
statusTextstring是否已签名的文案,写入 status 部件;适配器在作者未自行编写文字时把它填入节点。
toSvg() => string当前签名的独立 SVG 文档,与表单影子提交的是同一份;空签名为空串。
clear() => void
getRootProps() => T['element']
getLabelProps() => T['element']
getControlProps() => T['element']
getGuideProps() => T['element']
getPathProps() => T['element']
getClearTriggerProps() => T['button']
getStatusProps() => T['element']状态出口:一块 role=status 的活区域,签名与清空都会播报一次。
getHiddenInputProps() => T['input']表单出口:一份视觉隐藏的原生输入,随表单提交当前签名。

无障碍

键盘

规格出处:W3C APG

按键生效条件行为
Enter / Spacefocus on clear-trigger, 未禁用且非只读清空整块画布;按钮是原生 button,这两个键由平台翻成 click
Enter / Spaceheld in clear-trigger, 未禁用且非只读按住期间投影 data-pressed,与指针 :active 同一副按压面;抬起或失焦撤下

ARIA

以下属性由 connect 生成。

部件属性
controlaria-labeltranslations?.label
controlaria-labelledbylabel 部件的 id
controlrole'img'
guidearia-hidden'true'
clear-triggeraria-labeltranslations?.clearTrigger
statusaria-atomic'true'
statusaria-live'polite'
statusrole'status'
hidden-inputaria-hidden'true'
hidden-inputaria-invalid'true' | 'false'
  • 签名天然依赖指针,键盘和读屏无法完成。凡是要求签名的流程,必须同时提供一条不依赖指针的替代路径:打字签名、上传签名图或线下核验。只放一块画布等于把这些用户挡在流程之外。
  • 画布报告为 role="img",不是控件:它不接受键盘、不进入 Tab 序列,伪装成控件只会让读屏用户进入无法操作的位置。
  • 画布的名称来自 label 部件;未渲染标题时退回 translations.label
  • 是否已签名由 status 部件播报。画布是 role="img",名称固定,签名、清空、表单重置后读屏读出的都是同一句,用户无法确认笔迹是否保留。statusrole="status" 的活动区域,值每变一次播报一次,文案使用 translations.statusEmpty / translations.statusSigned。要求签名的表单请渲染它。
  • 基准线是纯画面,带 aria-hidden,读屏不读。没有 translations.guide 文案:给装饰线命名只会增加无信息量的播报。
  • 清空按钮是原生 <button>,Enter / Space 由平台激活;按钮内只有图标时读屏读 translations.clearTrigger

样式参考

皮肤

@xihan-ui/styles/signature-pad.css 使用 [data-scope="signature-pad"][data-part="root"] 部件选择器,位于 xihan.components 层。覆盖样式使用 xihan.overrides

数据属性

connect 生成;条件不成立时不输出无值属性。

部件属性
rootdata-disabled''(条件成立时才出现)
rootdata-drawing''(条件成立时才出现)
rootdata-empty''(条件成立时才出现)
rootdata-invalid''(条件成立时才出现)
rootdata-readonly''(条件成立时才出现)
labeldata-disabled''(条件成立时才出现)
controldata-disabled''(条件成立时才出现)
controldata-drawing''(条件成立时才出现)
controldata-empty''(条件成立时才出现)
controldata-invalid''(条件成立时才出现)
controldata-readonly''(条件成立时才出现)
guidedata-disabled''(条件成立时才出现)
pathdata-empty''(条件成立时才出现)
clear-triggerdata-disabled''(条件成立时才出现)
clear-triggerdata-empty''(条件成立时才出现)
clear-triggerdata-pressed''(条件成立时才出现)
clear-triggerdata-xh-action-control''
clear-triggerdata-xh-action-display'always'
clear-triggerdata-xh-action-profile'text'
clear-triggerdata-xh-action-size'sm'
clear-triggerdata-xh-action-variant'outline'
statusdata-empty''(条件成立时才出现)
hidden-inputdata-disabled''(条件成立时才出现)

CSS 变量

本组件公开覆盖槽由独立皮肤的实际消费位生成;默认来源、作用部件和状态均与 CSS 同源。

变量部件CSS 属性状态默认来源说明
--xh-signature-pad-aspect-ratiocontrolaspect-ratiodefault5 / 2signature-pad 的 control 部件 aspect-ratio 覆盖槽。
--xh-signature-pad-bgcontrolbackgrounddefaulttransparentsignature-pad 的 control 部件 background 覆盖槽。
--xh-signature-pad-bg-disabledcontrolbackgrounddisabled--xh-bg-subtlesignature-pad 的 control 部件 background 覆盖槽。
--xh-signature-pad-bg-readonlycontrolbackgrounddisabled
not([data-disabled])
readonly
--xh-bg-subtlesignature-pad 的 control 部件 background 覆盖槽。
--xh-signature-pad-bordercontrolborderdefault--xh-border-controlsignature-pad 的 control 部件 border 覆盖槽。
--xh-signature-pad-border-disabledcontrolborder-colordisabled--xh-border-defaultsignature-pad 的 control 部件 border-color 覆盖槽。
--xh-signature-pad-border-drawingcontrolborder-colordrawing--xh-border-control-hoversignature-pad 的 control 部件 border-color 覆盖槽。
--xh-signature-pad-clear-bgclear-triggerbackground-colordefault
focus-visible
--xh-_action-variant-bg-focus-visible
--xh-_action-variant-bg-rest
signature-pad 的 clear-trigger 部件 background-color 覆盖槽。
--xh-signature-pad-clear-bg-activeclear-triggerbackground-colordisabled
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-_action-variant-bg-pressedsignature-pad 的 clear-trigger 部件 background-color 覆盖槽。
--xh-signature-pad-clear-bg-disabledclear-triggerbackground-colordisabled--xh-_action-variant-bg-disabledsignature-pad 的 clear-trigger 部件 background-color 覆盖槽。
--xh-signature-pad-clear-bg-hoverclear-triggerbackground-colordisabled
hover
loading
not([data-disabled])
not([data-loading])
--xh-_action-variant-bg-hoversignature-pad 的 clear-trigger 部件 background-color 覆盖槽。
--xh-signature-pad-clear-borderclear-triggerborder
border-color
default
focus-visible
--xh-_action-variant-border-focus-visible
--xh-_action-variant-border-rest
signature-pad 的 clear-trigger 部件 border、border-color 覆盖槽。
--xh-signature-pad-clear-border-hoverclear-triggerborder-colordisabled
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-_action-variant-border-hover
--xh-_action-variant-border-pressed
signature-pad 的 clear-trigger 部件 border-color 覆盖槽。
--xh-signature-pad-clear-fgclear-triggercolordefault
disabled
focus-visible
hover
is(:active, [data-pressed])
loading
not([data-disabled])
not([data-loading])
pressed
--xh-_action-variant-fg-focus-visible
--xh-_action-variant-fg-hover
--xh-_action-variant-fg-pressed
--xh-_action-variant-fg-rest
signature-pad 的 clear-trigger 部件 color 覆盖槽。
--xh-signature-pad-clear-fg-emptyclear-triggercolorempty--xh-fg-mutedsignature-pad 的 clear-trigger 部件 color 覆盖槽。
--xh-signature-pad-clear-font-sizeclear-triggerfont-sizedefault--xh-_action-profile-font-sizesignature-pad 的 clear-trigger 部件 font-size 覆盖槽。
--xh-signature-pad-clear-gapclear-triggergapdefault--xh-_action-profile-gapsignature-pad 的 clear-trigger 部件 gap 覆盖槽。
--xh-signature-pad-clear-hclear-triggerblock-sizedefault--xh-_action-profile-visual-sizesignature-pad 的 clear-trigger 部件 block-size 覆盖槽。
--xh-signature-pad-clear-icon-sizeclear-trigger--xh-icon-sizedefault--xh-_action-profile-glyph-sizesignature-pad 的 clear-trigger 部件 --xh-icon-size 覆盖槽。
--xh-signature-pad-clear-pxclear-triggerpadding-inlinedefault--xh-_action-profile-padding-inlinesignature-pad 的 clear-trigger 部件 padding-inline 覆盖槽。
--xh-signature-pad-clear-radiusclear-triggerborder-radiusdefault--xh-shape-controlsignature-pad 的 clear-trigger 部件 border-radius 覆盖槽。
--xh-signature-pad-clear-shadow-hoverclear-triggerbox-shadowdisabled
hover
loading
not([data-disabled])
not([data-loading])
nonesignature-pad 的 clear-trigger 部件 box-shadow 覆盖槽。
--xh-signature-pad-control-border-invalidcontrolborder-colorinvalid--xh-border-invalidsignature-pad 的 control 部件 border-color 覆盖槽。
--xh-signature-pad-gaplabel
root
gap
margin-block-end
default--xh-space-2signature-pad 的 label、root 部件 gap、margin-block-end 覆盖槽。
--xh-signature-pad-guide-strokeguidestrokedefault--xh-border-controlsignature-pad 的 guide 部件 stroke 覆盖槽。
--xh-signature-pad-inkpathfilldefault--xh-fg-defaultsignature-pad 的 path 部件 fill 覆盖槽。
--xh-signature-pad-label-fglabelcolordefault--xh-fg-defaultsignature-pad 的 label 部件 color 覆盖槽。
--xh-signature-pad-label-fg-disabledlabelcolordisabled--xh-fg-subtlesignature-pad 的 label 部件 color 覆盖槽。
--xh-signature-pad-label-font-sizelabelfont-sizedefault--xh-text-label-sizesignature-pad 的 label 部件 font-size 覆盖槽。
--xh-signature-pad-label-font-weightlabelfont-weightdefault--xh-text-label-weightsignature-pad 的 label 部件 font-weight 覆盖槽。
--xh-signature-pad-label-gaplabelmargin-block-enddefault--xh-space-1signature-pad 的 label 部件 margin-block-end 覆盖槽。
--xh-signature-pad-radiuscontrolborder-radiusdefault--xh-shape-controlsignature-pad 的 control 部件 border-radius 覆盖槽。
--xh-signature-pad-status-fgstatuscolordefault--xh-fg-mutedsignature-pad 的 status 部件 color 覆盖槽。
--xh-signature-pad-status-font-sizestatusfont-sizedefault--xh-text-secondary-sizesignature-pad 的 status 部件 font-size 覆盖槽。

动效

background · border-colortransition 过渡。时长与缓动读动效令牌,改令牌即改全局节奏。

系统开启减弱动效时由令牌层统一收敛,皮肤不另作判断。

响应式

  • 画布宽度铺满外层容器,高度由宽高比(--xh-signature-pad-aspect-ratio,默认 5 / 2)决定,窄屏上自动变矮。
  • 签名过程中转屏、拖动面板改变宽度时,已有笔迹按 viewBox 整体缩放,后续新笔与它落在同一套坐标。
  • 触摸设备上画布关闭浏览器的滚动与缩放手势,避免手指划动时页面滚动。

RTL

皮肤用逻辑属性排布(inline-start 一族),dir="rtl" 下自动镜像。

  • 画布与基准线不区分左右:笔迹按落笔坐标记录,方向由书写者决定。
  • 标题与清空按钮的排布跟随文档方向,皮肤全部使用逻辑属性。

Released under The MIT License