手寫簽名板
canvas 手寫簽名:筆壓粗細、清除重寫、匯出 PNG。
以 canvas 2d 與 Pointer Events 實作的手寫簽名板:依書寫速度(或觸控筆真實筆壓)即時改變線寬,模擬鋼筆筆壓;筆畫以堆疊儲存,支援逐筆復原、一鍵清除與下載 PNG,並附虛線簽名底線與「請簽名」浮水印。
把這個效果裝進你的專案
在專案資料夾執行指令,CLI 會加入原始碼與依賴套件,再依下方使用範例引用元件。
npx webberui@latest add signature-padSignature Pad 是用 React、canvas 2D 與 Motion 做的手寫簽名(電子簽名)元件,可放進表單收集簽名,結果以 PNG dataURL 透過 onChange 回傳,也能直接下載成圖檔。
Playground
即時調整 props、程式碼片段同步更新——直接試出你要的樣子再複製。
<SignaturePad/>
完整範例包含預覽的排版與資料。先安裝元件,再把範例檔貼入你的專案。
適用情境
- 線上合約、委託書或同意書的簽署步驟
- 外送、物流 App 的簽收確認畫面
- 診所、健身房、租借服務的入會或報到表單
- 平板上的現場簽到、驗收單據
- 不適合:只需要使用者勾選同意、不需保留筆跡影像的流程,用一般勾選框即可
安裝
npx webberui@latest add https://webberui.com/r/signature-pad.json或在 components.json 設定 registries 後,改用 @webberui/signature-pad 安裝。
使用
import { SignaturePad } from "@/components/ui/signature-pad";
<SignaturePad
height={200}
onChange={(dataUrl) => {
// dataUrl 為 PNG dataURL,畫布為空時為 null
console.log(dataUrl);
}}
/>範例
存進表單狀態並在送出時檢查
onChange 在畫布為空時傳入 null,可以直接拿來判斷是否已簽名、控制送出按鈕。
"use client";
import * as React from "react";
import { SignaturePad } from "@/components/ui/signature-pad";
export function ConsentForm() {
const [signature, setSignature] = React.useState<string | null>(null);
return (
<form
onSubmit={(e) => {
e.preventDefault();
// signature 為 PNG dataURL
}}
>
<SignaturePad height={180} onChange={setSignature} />
<button type="submit" disabled={!signature}>
送出同意書
</button>
</form>
);
}上傳到 API
dataURL 可以用 fetch 轉成 Blob,再以 FormData 上傳到你的後端。
async function uploadSignature(dataUrl: string) {
const blob = await fetch(dataUrl).then((res) => res.blob());
const body = new FormData();
body.append("signature", blob, "signature.png");
await fetch("/api/signatures", { method: "POST", body });
}
<SignaturePad
onChange={(dataUrl) => {
if (dataUrl) void uploadSignature(dataUrl);
}}
/>透明背景與自訂筆跡
要把簽名疊在 PDF 或其他文件上時,用 exportBackground="transparent" 輸出透明 PNG,並以 strokeColor 指定藍色墨水。
<SignaturePad
strokeColor="#1d4ed8"
minWidth={1}
maxWidth={3}
exportBackground="transparent"
fileName="contract-signature"
placeholder="請在此簽署"
/>Props
| Prop | 型別 | 預設值 | 說明 |
|---|---|---|---|
strokeColor | string | "currentColor" | 筆跡顏色;預設落筆當下讀取容器文字顏色,自動適應深淺色主題 |
minWidth | number | 1.5 | 快速滑動時的最細線寬(px) |
maxWidth | number | 4 | 緩慢下筆時的最粗線寬(px) |
height | number | 220 | 簽名區高度(px),寬度自動填滿容器 |
placeholder | string | "請簽名" | 尚未落筆時顯示的浮水印文字 |
fileName | string | "signature" | 下載 PNG 時的檔名(不含副檔名) |
exportBackground | string | "#ffffff" | 匯出 PNG 的底色;設為 "transparent" 可輸出透明背景 |
onChange | (dataUrl: string | null) => void | — | 每次筆畫結束、復原或清除後回呼;畫布為空時傳入 null |
className | string | — | 透傳到最外層容器 |
可及性
- 簽名區設有
role="img"與aria-label="手寫簽名區",工具列按鈕皆為原生button,附文字標籤與disabled狀態 - 使用 Pointer Events 並設定
touch-action: none,滑鼠、手指與觸控筆皆可書寫;觸控筆會採用真實筆壓值 - 使用者系統開啟「減少動態效果」時,浮水印過場與按鈕微縮動畫停用,書寫功能完全不受影響
- 手寫簽名本質上需要指向裝置;在正式表單中請提供替代的同意方式(例如勾選框加輸入姓名)
常見問題
可以在 Next.js App Router 使用嗎?
可以。元件檔案開頭已有 "use client",在 Server Component 頁面中照常 import 即可。
怎麼取得簽名圖片?
每次筆畫結束、復原或清除後,onChange 會收到 PNG dataURL,畫布為空時為 null。使用者也可以按工具列的「下載 PNG」,檔名由 fileName 決定。
匯出的圖片解析度夠嗎?
畫布會依 devicePixelRatio 設定像素緩衝區,容器尺寸改變時重新配置並重繪所有筆畫,所以在高解析度螢幕上匯出的 PNG 也會保持銳利。
觸控筆的筆壓有支援嗎?
有。pointerType 為 pen 且有壓力值時,線寬依真實筆壓在 minWidth 與 maxWidth 之間變化;滑鼠與手指則依書寫速度換算,越快越細。
需要哪些依賴?
motion、lucide-react、clsx 與 tailwind-merge。不想用 CLI 的話,可以在安裝區的「手動」分頁照步驟複製原始碼。