Angular 签名字段 | ng-hub-ui-signature
可访问的 Angular 签名字段,将鼠标、触摸和手写笔输入保存为可缩放 SVG 表单值,并支持撤销、重做和 PNG 导出。
最后更新 2026年9月3日
概览
团队为何搜索这个库
当表单需要可复用、可访问且可移植的签名区域时,请使用 ng-hub-ui-signature。
安装
npm install ng-hub-ui-signature跳转到
适用于
- 合同接受
- 交付确认
- 同意表单
- 审批流程
关于 signature
ng-hub-ui-signature 是基于 Pointer Events 的 standalone Angular 表单字段。它将鼠标、触摸和手写笔笔画保存为 SVG ControlValueAccessor 值,可恢复已保存的签名,并提供清除、撤销、重做和 PNG 导出。
功能指南
Capture and edit signatures
Pointer input works with a mouse, touch screen or pen, and arrow keys with Space carry the same stroke for anyone signing without a pointer. Either way the field keeps a scalable SVG history for undo, redo and form persistence.
示例:
签名
Draw a signature and use the built-in history actions.
Sign with the keyboard: arrow keys move the pen, holding Shift moves it further, Space or Enter lowers and lifts it, and Escape discards the stroke in progress.
代码
导入:
模板:
组件:
Signing with the keyboard
Arrow keys carry the pen, Space or Enter lower and lift it, Escape discards the stroke in progress — and what comes out is an ordinary stroke, which is why the draw outputs carry PointerEvent | KeyboardEvent.
示例:
用键盘签名
该字段无需指针即可操作:方向键移动笔尖,Space 或 Enter 落笔与抬笔,Escape 丢弃正在书写的笔画。产生的笔画与手绘的完全相同,这正是 (drawStart) 携带 PointerEvent | KeyboardEvent 的原因——差异只保留在事件负载中。
Sign with the keyboard: arrow keys move the pen, holding Shift moves it further, Space or Enter lowers and lifts it, and Escape discards the stroke in progress.
- ←↑↓→移动笔尖
- Shift + →移动得更远
- Space / Enter落笔,再次按下则抬笔并提交笔画
- Esc丢弃正在书写的笔画
代码
导入:
模板:
组件:
Signature as a form control
The field is a ControlValueAccessor whose value is the SVG it serializes itself into: the required marker and the error message come from the control's validators, and the success message needs both [showValid] and [validFeedback].
示例:
作为表单控件的签名
该字段是一个 ControlValueAccessor,其值就是它自己序列化出的 SVG。必填标记与错误信息来自控件的验证器;成功信息需要同时提供 [showValid] 与 [validFeedback]。
代码
导入:
模板:
组件:
Draw events and stroke data
The drawing lifecycle is observable through (drawStart) and (drawEnd), the field answers isEmpty() without parsing its SVG, and toStrokes() / fromStrokes() move the geometry itself — which the SVG form value cannot carry.
示例:
绘制事件与笔画数据
用 (drawStart) 和 (drawEnd) 响应绘制过程,用 isEmpty() 查询字段状态,并用 toStrokes() 和 fromStrokes() 在字段之间搬运几何数据本身。
Sign with the keyboard: arrow keys move the pen, holding Shift moves it further, Space or Enter lowers and lifts it, and Escape discards the stroke in progress.
Sign with the keyboard: arrow keys move the pen, holding Shift moves it further, Space or Enter lowers and lifts it, and Escape discards the stroke in progress.
代码
导入:
模板:
组件:
Reactive external translations
The action-label contract accepts strings or reactive sources, so Transloco and ngx-translate language changes update the field without a direct package dependency.
示例:
Transloco 和 ngx-translate
绑定响应式翻译流,以便在语言切换时更新操作。
Sign with the keyboard: arrow keys move the pen, holding Shift moves it further, Space or Enter lowers and lifts it, and Escape discards the stroke in progress.
代码
导入:
模板:
组件:
核心特性
最近更改
Version 22.6.1 - 9/3/26, 12:00 AM
fixed: Server-side rendering no longer throws. writeValue() repaints the canvas and runs whenever a reactive form binds a value — including during prerendering, where the server DOM shim throws NotYetImplemented from canvas.getContext('2d') instead of returning null, so the existing null check never ran. redraw() now returns early outside the browser; the afterNextRender hook already repaints once there are pixels.
Version 22.5.0 - 9/1/26, 12:00 AM
fixed: [labelType] is read. It was declared, compiled, type-checked and never looked at, so a team migrating a horizontal form bound the input, saw a stacked label, and went hunting through ng-hub-ui-forms for a bug that was not there. 'horizontal' now places the label beside the drawing surface, with the action row, helper text and feedback stacked in the second column. 'floating' still falls back to stacked, deliberately: it reuses the space an empty text control's value would occupy and is driven by :placeholder-shown, and a label parked inside the box would sit on top of the ink the moment anyone signed.
fixed: The validation state shows on the drawing surface. The --invalid and --valid classes were bound on the root and styled by nothing, so a required-but-empty signature printed an error message under a canvas that looked exactly like a valid one. The canvas now takes the danger border and ring when touched and invalid, and the success pair when [showValid] is on and the field is valid, from the shared --hub-form-* contract the rest of the family uses. If you wrote the workaround the migration guide recommended, delete it: it now collides with the component rule at equal specificity.
added: A live theming demo exercising all eleven --hub-signature-* slots through hub-signature-theme(). Building it surfaced a trap now documented: setting the tokens on a wrapper element does nothing, because the component declares every slot on the field element itself and a property declared on an element always beats one inherited from an ancestor. That is why the mixin emits "<your scope> :where(.hub-signature)" rather than relying on inheritance.
Version 22.4.0 - 9/1/26, 12:00 AM
fixed: The visible label now names the drawing surface. The template rendered <label for> pointing at the canvas, and for associates only with labelable elements — a <canvas> is none of them, so the attribute was inert: no association, clicking did nothing, and the name came entirely from [ariaLabel]. The surface is named with aria-labelledby, the only mechanism that works on a non-labelable element and the only one unaffected by role="application". aria-labelledby and aria-label are now mutually exclusive, because the first outranks the second outright and emitting both would leave one permanently unreachable. Clicking the label focuses the surface.
fixed: [ariaLabel] goes through the translation dictionary. It was a hardcoded English literal with no HUBUI.SIGNATURE.* key behind it, so an application that localized every button still had its drawing surface announce itself in English. It now resolves through the explicit input, then [labels] / provideHubSignature(), then HUBUI.SIGNATURE.ARIA_LABEL, then the English fallback; its default changed from Signature to the empty string so that unset is distinguishable.
changed: The accessible name comes from [label] when there is one, and [ariaLabel] is the fallback for a bare surface. This is what closes WCAG 2.5.3: translating [ariaLabel] alone would only have made agreement possible, still asking every consumer to pass the same string twice. Binding [ariaLabel] beside a [label] no longer does anything — remove it. HubSignatureLabels also gained a required ariaLabel member; breaking, see BREAKING_CHANGES.md.
Version 22.3.0 - 9/1/26, 12:00 AM
added: A keyboard path to sign. The canvas had carried tabindex="0" since 22.0.0 over pointer-only handlers, so the field was focusable and unusable — a required control no keyboard-only user could satisfy. Arrow keys now carry a visible pen, Space or Enter lower and lift it, Escape abandons the stroke. It goes through the same internal begin/commit pair the pointer does, so it yields the same stroke, the same toSvg() output and the same reported value. The surface carries role="application" because a canvas with tabindex is not a form control, and screen readers would otherwise consume the arrows for document navigation; the instructions are announced through aria-describedby under HUBUI.SIGNATURE.KEYBOARD_HINT.
added: [validFeedback] is rendered. It existed on HubFieldControl all along, so it compiled and type-checked on <hub-signature> and then did nothing. It now renders the same .hub-field__feedback--valid block as every other field of the family.
added: hub-signature-theme() reaches all eleven --hub-signature-* tokens instead of five. The six new parameters are appended after the original five so existing positional includes keep resolving to the same tokens, and an argument-less include now raises a Sass @warn rather than compiling silently to nothing.
fixed: pointercancel no longer commits the partial stroke. It was wired to the pen-up handler, so an OS gesture, a scroll takeover or palm rejection pushed a half-drawn stroke onto the history, reported it to the form and emitted (drawEnd). A cancelled interaction is now discarded, and losing focus mid-stroke takes the same path.
fixed: The default currentColor ink is resolved before capture. The canvas 2D context cannot parse CSS-context keywords, so the ink fell back to black whatever the surrounding colour, and the literal was written into the persisted SVG — leaving archived signatures with no fixed colour at all. It is now resolved with getComputedStyle() when the stroke opens, which is also what makes hub-signature-theme($color) reach the ink as documented.
changed: (drawStart) and (drawEnd) emit HubSignatureDrawEvent — PointerEvent | KeyboardEvent — because drawing is no longer pointer-exclusive, and HubSignatureLabels gained a required keyboardHint member. Both are breaking; see BREAKING_CHANGES.md.
Version 22.2.0 - 9/1/26, 12:00 AM
added: isEmpty(), so a form can validate the field without parsing the serialized SVG — previously the only way to ask whether anything had been drawn.
added: toStrokes() / fromStrokes(), exposing the committed strokes as structured HubSignatureStroke[]. Deliberately not named toData / fromData: angular2-signaturepad uses those names for an incompatible payload, and the same name with a different shape would let a migration compile and then fail silently.
added: (drawStart) and (drawEnd) outputs, emitted around a user stroke, so a host can react to drawing activity without polling the value.
changed: The hub-signature-theme() mixin documents itself where the tooling can read it. Its header used a block comment, which the generator skips, so the library page showed no theming section at all despite the mixin having shipped since 22.0.0. Comment-only — the emitted declarations are byte-identical.
Version 22.1.1 - 8/17/26, 12:00 AM
fixed: The published package declared no licence. A registry reports an absent `license` field as unlicensed, which legally reads as all rights reserved. The intent was always MIT; it is now stated in `package.json` and carried in a `LICENSE` file that ships with the package.
Version 22.1.0 - 8/14/26, 12:00 AM
fixed: The field now really inherits the ng-hub-ui-forms contract. The --hub-signature-* slots defaulted to a --hub-field-* family that no library declares, so they always fell through to their sys/ref fallbacks and a form themed with --hub-input-bg left its signature field untouched. They now read the canonical tokens of the .hub-field__* shell: --hub-input-* for the drawing surface, --hub-label-* for the label and --hub-form-disabled-opacity for the disabled state.
added: The eleven --hub-signature-* tokens are documented in the design-system token spec, so they now appear in the library reference table.
Version 22.0.0 - 8/14/26, 12:00 AM
added: Initial signature field release.