npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

vue3-signature

v0.4.1

Published

Electronic signature for Vue3

Readme

npm version npm downloads license vue3 signature_pad

🎮 在线体验✨ 特性🚀 快速开始📖 文档💡 示例


🎮 在线体验

无需安装,直接在浏览器中体验 Vue3 Signature:

👉 GitHub Pages 在线演示 - 在浏览器中试用所有功能!

演示展示了组件的所有功能,包括绘制、保存、撤销、水印等。

🚀 部署你自己的演示

想要部署自己的演示吗?查看我们的部署指南了解如何部署到 GitHub Pages 的详细说明。

📁 新功能:图片导入

在线演示现在支持导入图片功能:

  • 📁 上传本地图片文件
  • 🔗 从URL加载图片
  • 🖼️ 一键尝试示例图片

非常适合编辑现有签名、添加标注或使用模板!

✨ 特性

🎯 核心技术

基于 signature_pad 构建 - 这是由 Szymon Nowak 开发的行业标准 HTML5 Canvas 平滑签名绘制库,拥有 13k+ GitHub 星标,可靠性经过验证。

⚡ 主要特性

  • 📱 触摸和鼠标支持 - 在移动设备和桌面设备上无缝工作
  • 🎨 可自定义 - 完全可定制的画笔颜色、背景和签名样式
  • 💾 多种导出格式 - 导出签名为 PNG、JPEG 或 SVG 格式
  • 🔄 撤销支持 - 简单的撤销功能,提供更好的用户体验
  • 🖼️ 水印支持 - 为签名添加自定义水印
  • 🚫 禁用模式 - 在可编辑和只读状态之间切换
  • 📐 响应式 - 自动适应容器大小
  • 🎯 TypeScript 支持 - 包含完整的 TypeScript 类型定义
  • 轻量级 - 小巧的包体积,无不必要的依赖
  • 🌍 跨平台 - 适用于所有现代浏览器和移动设备

演示

🚀 快速开始

安装

# 使用 npm
npm install vue3-signature

# 使用 yarn
yarn add vue3-signature

# 使用 pnpm
pnpm add vue3-signature

基本使用

步骤 1: 全局注册组件

// main.js
import { createApp } from "vue";
import Vue3Signature from "vue3-signature";
import App from "./App.vue";

createApp(App).use(Vue3Signature).mount("#app");

步骤 2: 在组件中使用

<template>
  <div>
    <Vue3Signature
      ref="signature"
      :sigOption="options"
      :w="'800px'"
      :h="'400px'"
    />

    <div class="buttons">
      <button @click="save">保存为 PNG</button>
      <button @click="clear">清空</button>
      <button @click="undo">撤销</button>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive } from "vue";

const signature = ref(null);
const options = reactive({
  penColor: "rgb(0, 0, 0)",
  backgroundColor: "rgb(255, 255, 255)",
});

const save = () => {
  const png = signature.value.save();
  console.log(png); // base64 data URL
};

const clear = () => {
  signature.value.clear();
};

const undo = () => {
  signature.value.undo();
};
</script>

📖 文档

属性

| 属性 | 类型 | 默认值 | 描述 | | --------------- | --------- | ----------------------------------------------------------------- | ------------------------------------- | | sigOption | Object | {penColor: "rgb(0, 0, 0)", backgroundColor: "rgb(255,255,255)"} | 签名板选项,包括画笔颜色和背景颜色 | | w | String | "100%" | 签名板宽度(例如 "100%""500px")| | h | String | "100%" | 签名板高度(例如 "100%""300px")| | clearOnResize | Boolean | false | 窗口调整大小时是否清空画布 | | waterMark | Object | {} | 水印配置(参见水印选项)| | disabled | Boolean | false | 禁用/启用签名输入 | | defaultUrl | String | "" | 要在画布上显示的默认图像 URL |

💡 sigOption 支持 signature_pad 提供的全部参数。组件会深度监听该对象,自动重建签名板并尽量保留当前笔画。

方法

通过组件 ref 可以访问 signature_pad 的所有公开 API:

| 方法 | 参数 | 返回值 | 描述 | | --------------------------------- | ------------------------------------------------------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------ | | save(format?, encoderOptions?) | 同 toDataURL | string | toDataURL 的兼容别名,方便旧版本升级。 | | toDataURL(format?, encoderOptions?) | 与 signature_pad#toDataURL 完全一致 | string | 按 PNG/JPEG/SVG 导出签名,支持传入编码参数。 | | toSVG(options?) | options?: ToSVGOptions | string | 输出 SVG 字符串,可选包含背景色或 fromDataURL 导入的图片。 | | clear() | - | void | 使用当前背景色清空画布。 | | redraw() | - | void | 重新渲染当前保存的笔画/导入的图片。 | | isEmpty() | - | boolean | 判断画布是否为空。 | | undo(steps = 1) | steps?: number | void | 撤销最近的若干笔画。 | | toData() | - | PointGroup[] | 获取 signature_pad 的原始笔画数据。 | | fromData(pointGroups, options?) | pointGroups: PointGroup[], options?: FromDataOptions | void | 根据原始笔画数据绘制,可控制是否清空画布。 | | fromDataURL(url, options?) | url: string, options?: FromDataUrlOptions | Promise<void> | 与 signature_pad#fromDataURL 完全一致的导入能力。 | | addWaterMark(options) | options: WaterMarkOption | void | 便捷水印工具,保留原有功能。 | | trim(options?) | options?: TrimOptions | TrimResult \| null | 克隆画布、裁剪留白并返回离屏结果,不会影响用户看到的内容。 | | toTrimmedDataURL(format?, encoderOptions?) | format?: string, encoderOptions?: number | string | 仅返回裁剪后的 data URL,内部复用 trim。 | | enable() / disable() | - | void | 直接调用底层的 on()/off(),用于切换编辑状态。 | | addEventListener(...) | 与 EventTarget#addEventListener 相同 | void | 绑定 signature_pad 的底层事件。 | | removeEventListener(...) | 与 EventTarget#removeEventListener 相同 | void | 移除事件监听。 | | getInstance() | - | SignaturePad? | 获取底层 SignaturePad 实例,方便直接访问所有属性/方法。 |

事件

| 事件 | 载荷 | 说明 | | ------------------- | ----------------- | ------------------------------------------------ | | ready | SignaturePad | 组件初始化完成并调整完大小后触发。 | | begin / end | void | 兼容旧版本的 onBegin / onEnd。 | | beginStroke | SignatureEvent | 笔画开始前触发,可通过 preventDefault 取消。 | | beforeUpdateStroke| SignatureEvent | 笔画更新前回调。 | | afterUpdateStroke | SignatureEvent | 笔画更新后回调。 | | endStroke | SignatureEvent | 笔画结束时触发。 |

签名选项

import type { Options as SignaturePadOptions } from "signature_pad";

type SigOption = SignaturePadOptions;

// 你可以传入 signature_pad 支持的所有配置,例如:
// dotSize?: number;
// minWidth?: number;
// maxWidth?: number;
// minDistance?: number;
// throttle?: number;
// velocityFilterWeight?: number;
// penColor?: string;
// backgroundColor?: string;
// compositeOperation?: GlobalCompositeOperation;
// canvasContextOptions?: CanvasRenderingContext2DSettings;

水印选项

interface WaterMarkOption {
  text?: string; // 水印文本(默认:"")
  font?: string; // 字体样式(默认:"20px sans-serif")
  style?: string; // 样式类型:"all" | "stroke" | "fill"(默认:"fill")
  fillStyle?: string; // 填充颜色(默认:"#333")
  strokeStyle?: string; // 描边颜色(默认:"#333")
  x?: number; // 填充文本 X 位置(默认:20)
  y?: number; // 填充文本 Y 位置(默认:20)
  sx?: number; // 描边文本 X 位置(默认:40)
  sy?: number; // 描边文本 Y 位置(默认:40)
}

裁剪结果与选项

interface TrimResult {
  canvas: HTMLCanvasElement;            // 离屏画布(已裁剪)
  dataUrl: string;                     // 方便直接使用的裁剪后 data URL
  bounds: { x: number; y: number; width: number; height: number }; // 裁剪区域(像素)
}

interface TrimOptions {
  format?: string;         // 传给 canvas.toDataURL 的格式
  encoderOptions?: number; // JPEG/WebP 质量
  backgroundColor?: string;// 覆盖用于检测留白的背景色
}

💡 示例

保存为不同格式

<template>
  <Vue3Signature ref="signature" :w="'800px'" :h="'400px'" />

  <button @click="saveAsPNG">保存为 PNG</button>
  <button @click="saveAsJPEG">保存为 JPEG</button>
  <button @click="saveAsSVG">保存为 SVG</button>
</template>

<script setup>
import { ref } from "vue";

const signature = ref(null);

const saveAsPNG = () => {
  const dataUrl = signature.value.save(); // 或 save('image/png')
  downloadImage(dataUrl, "signature.png");
};

const saveAsJPEG = () => {
  const dataUrl = signature.value.save("image/jpeg");
  downloadImage(dataUrl, "signature.jpg");
};

const saveAsSVG = () => {
  const dataUrl = signature.value.save("image/svg+xml");
  downloadImage(dataUrl, "signature.svg");
};

const downloadImage = (dataUrl, filename) => {
  const link = document.createElement("a");
  link.href = dataUrl;
  link.download = filename;
  link.click();
};
</script>

自定义画笔颜色和样式

<template>
  <div>
    <div class="color-picker">
      <button @click="setPenColor('#000000')">黑色</button>
      <button @click="setPenColor('#0000ff')">蓝色</button>
      <button @click="setPenColor('#ff0000')">红色</button>
      <button @click="setPenColor('#00ff00')">绿色</button>
    </div>

    <Vue3Signature
      ref="signature"
      :sigOption="options"
      :w="'100%'"
      :h="'400px'"
    />
  </div>
</template>

<script setup>
import { ref, reactive } from "vue";

const signature = ref(null);
const options = reactive({
  penColor: "rgb(0, 0, 0)",
  backgroundColor: "rgb(255, 255, 255)",
  minWidth: 1,
  maxWidth: 3,
});

const setPenColor = (color) => {
  options.penColor = color;
};
</script>

<style scoped>
.color-picker {
  margin-bottom: 10px;
}

.color-picker button {
  margin-right: 10px;
  padding: 8px 16px;
  border-radius: 4px;
  border: 1px solid #ddd;
  cursor: pointer;
}
</style>

添加水印

<template>
  <Vue3Signature ref="signature" :w="'800px'" :h="'400px'" />
  <button @click="addWatermark">添加水印</button>
</template>

<script setup>
import { ref } from "vue";

const signature = ref(null);

const addWatermark = () => {
  signature.value.addWaterMark({
    text: "机密",
    font: "30px Arial",
    style: "all",
    fillStyle: "rgba(255, 0, 0, 0.3)",
    strokeStyle: "rgba(255, 0, 0, 0.5)",
    x: 100,
    y: 200,
    sx: 102,
    sy: 202,
  });
};
</script>

禁用模式(只读)

<template>
  <div>
    <Vue3Signature
      ref="signature"
      :disabled="isDisabled"
      :w="'800px'"
      :h="'400px'"
    />

    <button @click="toggleDisabled">
      {{ isDisabled ? "启用" : "禁用" }} 编辑
    </button>
  </div>
</template>

<script setup>
import { ref } from "vue";

const signature = ref(null);
const isDisabled = ref(false);

const toggleDisabled = () => {
  isDisabled.value = !isDisabled.value;
};
</script>

从 Data URL 加载

<template>
  <div>
    <Vue3Signature ref="signature" :w="'800px'" :h="'400px'" />

    <button @click="loadSignature">加载已保存的签名</button>
    <button @click="saveSignature">保存当前签名</button>
  </div>
</template>

<script setup>
import { ref } from "vue";

const signature = ref(null);
const savedSignature = ref("");

const saveSignature = () => {
  savedSignature.value = signature.value.save();
  alert("签名已保存!");
};

const loadSignature = () => {
  if (savedSignature.value) {
    signature.value.fromDataURL(savedSignature.value);
  } else {
    alert("未找到已保存的签名!");
  }
};
</script>

响应式设计

<template>
  <div class="signature-container">
    <Vue3Signature
      ref="signature"
      :w="'100%'"
      :h="'100%'"
      :clearOnResize="false"
    />
  </div>
</template>

<script setup>
import { ref } from "vue";

const signature = ref(null);
</script>

<style scoped>
.signature-container {
  width: 100%;
  height: 400px;
  max-width: 800px;
  margin: 0 auto;
  border: 2px solid #e0e0e0;
  border-radius: 8px;
  overflow: hidden;
}

@media (max-width: 768px) {
  .signature-container {
    height: 300px;
  }
}
</style>

移除签名周围的留白

<template>
  <Vue3Signature ref="signature" :w="'600px'" :h="'250px'" @end="trim" />
  <img v-if="trimmed" :src="trimmed" alt="裁剪后的签名" />
</template>

<script setup>
import { ref } from "vue";

const signature = ref(null);
const trimmed = ref("");

const trim = () => {
  const result = signature.value.trim();
  trimmed.value = result?.dataUrl ?? "";
};
</script>

该功能基于 issue #49 中的裁剪方案:组件复制当前画布,计算笔迹的最小包围盒并裁剪,只返回裁剪后的结果,不会修改用户正在编辑的画布。

🔧 高级用法

完整功能示例

<template>
  <div class="signature-app">
    <h2>电子签名板</h2>

    <div class="controls">
      <div class="control-group">
        <label>画笔颜色:</label>
        <input type="color" v-model="options.penColor" />
      </div>

      <div class="control-group">
        <label>背景颜色:</label>
        <input type="color" v-model="options.backgroundColor" />
      </div>

      <div class="control-group">
        <label>
          <input type="checkbox" v-model="isDisabled" />
          只读模式
        </label>
      </div>
    </div>

    <Vue3Signature
      ref="signature"
      :sigOption="options"
      :disabled="isDisabled"
      :w="'100%'"
      :h="'400px'"
      class="signature-pad"
    />

    <div class="button-group">
      <button @click="save('image/png')" class="btn btn-primary">
        💾 保存 PNG
      </button>
      <button @click="save('image/jpeg')" class="btn btn-primary">
        💾 保存 JPEG
      </button>
      <button @click="clear" class="btn btn-danger">🗑️ 清空</button>
      <button @click="undo" class="btn btn-secondary">↩️ 撤销</button>
      <button @click="addWatermark" class="btn btn-secondary">
        🔖 添加水印
      </button>
    </div>

    <div v-if="preview" class="preview">
      <h3>预览:</h3>
      <img :src="preview" alt="签名预览" />
    </div>
  </div>
</template>

<script setup>
import { ref, reactive } from "vue";

const signature = ref(null);
const preview = ref("");
const isDisabled = ref(false);

const options = reactive({
  penColor: "rgb(0, 0, 0)",
  backgroundColor: "rgb(255, 255, 255)",
});

const save = (format) => {
  if (signature.value.isEmpty()) {
    alert("请先提供签名。");
    return;
  }

  const dataUrl = signature.value.save(format);
  preview.value = dataUrl;

  // 下载图片
  const link = document.createElement("a");
  const extension = format === "image/jpeg" ? "jpg" : "png";
  link.download = `signature.${extension}`;
  link.href = dataUrl;
  link.click();
};

const clear = () => {
  signature.value.clear();
  preview.value = "";
};

const undo = () => {
  signature.value.undo();
};

const addWatermark = () => {
  signature.value.addWaterMark({
    text: new Date().toLocaleDateString(),
    font: "16px Arial",
    fillStyle: "rgba(0, 0, 0, 0.3)",
    x: 10,
    y: 30,
  });
};
</script>

<style scoped>
.signature-app {
  max-width: 900px;
  margin: 0 auto;
  padding: 20px;
}

.controls {
  display: flex;
  gap: 20px;
  margin-bottom: 20px;
  flex-wrap: wrap;
}

.control-group {
  display: flex;
  align-items: center;
  gap: 10px;
}

.signature-pad {
  border: 2px solid #e0e0e0;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.button-group {
  display: flex;
  gap: 10px;
  margin-top: 20px;
  flex-wrap: wrap;
}

.btn {
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 14px;
  transition: all 0.3s ease;
}

.btn-primary {
  background-color: #4caf50;
  color: white;
}

.btn-primary:hover {
  background-color: #45a049;
}

.btn-danger {
  background-color: #f44336;
  color: white;
}

.btn-danger:hover {
  background-color: #da190b;
}

.btn-secondary {
  background-color: #2196f3;
  color: white;
}

.btn-secondary:hover {
  background-color: #0b7dda;
}

.preview {
  margin-top: 30px;
  padding: 20px;
  background: #f5f5f5;
  border-radius: 8px;
}

.preview img {
  max-width: 100%;
  border: 1px solid #ddd;
  border-radius: 4px;
}
</style>

🌐 浏览器支持

Vue3 Signature 适用于所有现代浏览器:

  • ✅ Chrome(最新版)
  • ✅ Firefox(最新版)
  • ✅ Safari(最新版)
  • ✅ Edge(最新版)
  • ✅ Opera(最新版)
  • ✅ 移动浏览器(iOS Safari、Chrome Mobile)

🤝 贡献

欢迎贡献!请随时提交 Pull Request。

  1. Fork 本仓库
  2. 创建你的特性分支(git checkout -b feature/AmazingFeature
  3. 提交你的更改(git commit -m 'Add some AmazingFeature'
  4. 推送到分支(git push origin feature/AmazingFeature
  5. 打开一个 Pull Request

📝 更新日志

详细更新日志请查看 Releases

🙏 致谢与依赖

核心依赖

这个 Vue 3 组件是对 signature_pad 的封装 - 最流行和可靠的 HTML5 Canvas 签名库。

为什么选择 signature_pad?

  • 13,000+ GitHub 星标 - 受到全球数千名开发者的信赖
  • 🏆 行业标准 - 在无数生产应用中使用
  • 🎨 流畅绘制 - 采用先进的贝塞尔曲线插值算法,实现自然的签名效果
  • 📱 触摸优化 - 完美支持触摸屏和手写笔输入
  • 🔧 维护良好 - 活跃的开发和定期更新
  • 📦 轻量级 - 最小的体积,最大的功能

库信息:

通过使用 Vue3 Signature,你可以获得 signature_pad 的全部功能,同时享受 Vue 3 组合式 API 的简洁性。

📄 许可证

MIT License

Copyright (c) 2024 Shayne Wang


🔗 相关项目