Preview Updated 2026-05-10

SignaturePad 签名板

pointer events + canvas 平滑笔画签名板。quadratic-bezier 平滑、DPR 自动缩放、暴露 clear/toDataURL/toBlob/isEmpty 方法。

English translation pending This page hasn't been translated yet — falling back to Chinese. PRs welcome on GitHub.

基础用法

通过 ref 暴露 clear / toDataURL / toBlob / isEmpty。绘制平滑度用 quadratic-bezier 中点平均;DPR 自动缩放保证 retina 屏锐利。

背景 视口
src/App.vue
<script setup lang="ts">
import { ref } from 'vue';
import { CfSignaturePad, CfButton } from '@chufix-design/vue';

const pad = ref<InstanceType<typeof CfSignaturePad> | null>(null);
const empty = ref(true);
const preview = ref('');

function onChange(isEmpty: boolean) {
  empty.value = isEmpty;
  if (!isEmpty) preview.value = pad.value?.toDataURL() ?? '';
}
function doClear() {
  pad.value?.clear();
  preview.value = '';
}
function doExport() {
  if (!pad.value) return;
  preview.value = pad.value.toDataURL();
}
</script>
<template>
  <div class="sig-demo">
    <CfSignaturePad
      ref="pad"
      :height="160"
      stroke-color="#f8fafc"
      background="oklch(20% 0.012 260)"
      @change="onChange"
    />
    <div class="sig-demo__bar">
      <CfButton variant="tertiary" :disabled="empty" @click="doClear">清空</CfButton>
      <CfButton variant="primary" :disabled="empty" @click="doExport">导出 PNG</CfButton>
    </div>
    <img v-if="preview" :src="preview" class="sig-demo__preview" alt="signature" />
  </div>
</template>
<style scoped>
.sig-demo {
  display: grid;
  gap: 10px;
}
.sig-demo__bar {
  display: flex;
  gap: 8px;
}
.sig-demo__preview {
  max-height: 60px;
  background: #fff;
  border-radius: var(--r-2);
  padding: 4px;
}
</style>
<script setup>
import { ref } from 'vue';
import { CfSignaturePad, CfButton } from '@chufix-design/vue';

const pad = ref<InstanceType<typeof CfSignaturePad> | null>(null);
const empty = ref(true);
const preview = ref('');

function onChange(isEmpty) {
  empty.value = isEmpty;
  if (!isEmpty) preview.value = pad.value?.toDataURL() ?? '';
}
function doClear() {
  pad.value?.clear();
  preview.value = '';
}
function doExport() {
  if (!pad.value) return;
  preview.value = pad.value.toDataURL();
}
</script>
<template>
  <div class="sig-demo">
    <CfSignaturePad
      ref="pad"
      :height="160"
      stroke-color="#f8fafc"
      background="oklch(20% 0.012 260)"
      @change="onChange"
    />
    <div class="sig-demo__bar">
      <CfButton variant="tertiary" :disabled="empty" @click="doClear">清空</CfButton>
      <CfButton variant="primary" :disabled="empty" @click="doExport">导出 PNG</CfButton>
    </div>
    <img v-if="preview" :src="preview" class="sig-demo__preview" alt="signature" />
  </div>
</template>
<style scoped>
.sig-demo {
  display: grid;
  gap: 10px;
}
.sig-demo__bar {
  display: flex;
  gap: 8px;
}
.sig-demo__preview {
  max-height: 60px;
  background: #fff;
  border-radius: var(--r-2);
  padding: 4px;
}
</style>
import { useState } from 'react';
import { CfButton, CfSignaturePad } from '@chufix-design/react';

export default function Demo() {
  const [pad, setPad] = useState<InstanceType<typeof CfSignaturePad> | null>(null);
  const [empty, setEmpty] = useState(true);
  const [preview, setPreview] = useState('');

  function onChange(isEmpty: boolean) {
    setEmpty(isEmpty);
    if (!isEmpty) setPreview(pad?.toDataURL() ?? '');
  }
  function doClear() {
    pad?.clear();
    setPreview('');
  }
  function doExport() {
    if (!pad) return;
    setPreview(pad.toDataURL());
  }
  return (
    <>
      <div className="sig-demo">
          <CfSignaturePad ref="pad" height={160} stroke-color="#f8fafc" background="oklch(20% 0.012 260)" onChange={onChange} />
          <div className="sig-demo__bar">
            <CfButton variant="tertiary" disabled={empty} onClick={doClear}>清空</CfButton>
            <CfButton variant="primary" disabled={empty} onClick={doExport}>导出 PNG</CfButton>
          </div>
          <img v-if="preview" src={preview} className="sig-demo__preview" alt="signature" />
        </div>
    </>
  );
}
import { useState } from 'react';
import { CfButton, CfSignaturePad } from '@chufix-design/react';

export default function Demo() {
  const [pad, setPad] = useState<InstanceType<typeof CfSignaturePad> | null>(null);
  const [empty, setEmpty] = useState(true);
  const [preview, setPreview] = useState('');

  function onChange(isEmpty) {
    setEmpty(isEmpty);
    if (!isEmpty) setPreview(pad?.toDataURL() ?? '');
  }
  function doClear() {
    pad?.clear();
    setPreview('');
  }
  function doExport() {
    if (!pad) return;
    setPreview(pad.toDataURL());
  }
  return (
    <>
      <div className="sig-demo">
          <CfSignaturePad ref="pad" height={160} stroke-color="#f8fafc" background="oklch(20% 0.012 260)" onChange={onChange} />
          <div className="sig-demo__bar">
            <CfButton variant="tertiary" disabled={empty} onClick={doClear}>清空</CfButton>
            <CfButton variant="primary" disabled={empty} onClick={doExport}>导出 PNG</CfButton>
          </div>
          <img v-if="preview" src={preview} className="sig-demo__preview" alt="signature" />
        </div>
    </>
  );
}

API

Props

属性类型默认说明
widthnumber | string'100%'画板宽
heightnumber | string180画板高
strokeWidthnumber2笔画粗细
strokeColorstring当前 color颜色(默认读 CSS color
backgroundstring'transparent'背景;导出时会作为画板底色
disabledbooleanfalse禁用

Events

事件载荷说明
changeempty: boolean笔画结束 / 清空后
start一笔开始
end一笔结束

Exposed methods

通过 ref / forwardRef 暴露:

  • clear()
  • toDataURL(type?, quality?)
  • toBlob(type?, quality?): Promise<Blob \| null>
  • isEmpty(): boolean

反馈与讨论

SignaturePad 签名板 · Discussion

0
0 / 600
正在加载评论...