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 🙏

© 2026 – Pkg Stats / Ryan Hefner

signature-verifier

v1.0.0

Published

中文手写签名验证工具,基于像素重合度算法,支持 Web、微信小程序。

Readme

cn-signature-verifier

一个轻量级的中文手写签名验证工具。 基于“骨架重合度”算法,无需后端 AI 模型,纯前端实现。可以有效防止用户乱涂乱画。

安装

npm install cn-signature-verifier

运行示例 (Run Examples)

本项目提供了三个平台的完整示例,位于 examples/ 目录下。

  1. Web / H5: 直接用浏览器打开 examples/web-h5/index.html

  2. 微信小程序: 使用微信开发者工具导入 examples/wechat-native 目录。

  3. UniApp: 使用 HBuilderX 导入 examples/uniapp 目录运行。

这份文档详细介绍了核心算法函数 verifyPixels 的用法、参数配置以及调优指南。

此函数是整个库的“大脑”,它是纯 JavaScript 实现的,不依赖 DOM,因此既可以在 浏览器/小程序 中使用,也可以在 Node.js 后端环境中使用。


verifyPixels API 使用文档

verifyPixels 是核心校验函数,用于对比“用户手写笔迹”与“标准参考字体”的像素重合度。

1. 函数签名

import { verifyPixels } from 'cn-signature-verifier/src/core.js';

const result = verifyPixels(userPixels, refPixels, options);

2. 参数说明

| 参数名 | 类型 | 必填 | 说明 | | :--- | :--- | :--- | :--- | | userPixels | Uint8ClampedArray | 是 | 用户手写画布的像素数据。格式为 [r, g, b, a, ...] 的一维数组。 | | refPixels | Uint8ClampedArray | 是 | 标准参考字体画布的像素数据。格式同上。长度必须与 userPixels 一致。 | | options | Object | 否 | 配置项对象,用于调整验证的严格程度(详见下表)。 |

options 配置项详解

| 属性名 | 类型 | 默认值 | 描述 | 调优建议 | | :--- | :--- | :--- | :--- | :--- | | thresholdCoverage | Number | 0.12 | 覆盖率及格线 (0~1)。计算公式:重合像素 / 参考字像素表示用户笔迹覆盖了背景字的多少比例。 | 太严(难通过):调低 (如 0.08)。太松(空心字也过):调高 (如 0.2)。注意:如果背景字很粗,建议调低此值。 | | thresholdMess | Number | 0.65 | 越界率容忍上限 (0~1)。计算公式:越界像素 / 用户总像素表示用户的笔画有多少比例画在了字外面。 | 太严(连笔挂掉):调高 (如 0.75)。太松(乱画也能过):调低 (如 0.5)。 | | minInkRatio | Number | 0.005 | 最小笔墨量防止用户只点了一个点就提交。 | 通常保持默认即可。 |


3. 返回值说明

函数返回一个对象 Result

{
    pass: Boolean,      // 验证是否通过
    message: String,    // 提示信息 (例如 "验证通过" 或 "请勿乱涂乱画")
    score: {
        coverage: Number, // 实际覆盖率 (例如 0.15 代表 15%)
        mess: Number,     // 实际越界率 (例如 0.40 代表 40%)
        ink: Number       // 用户绘制的总像素点数
    }
}

4. 算法原理 (简述)

该算法基于 RGBA 像素分析

  1. 识别参考区:遍历 refPixels,Alpha 值 > 20 的区域被视为“标准答案区域”。
  2. 识别笔迹:遍历 userPixels,识别深色像素(R < 150 且 Alpha > 50)为“用户笔迹”。
  3. 对比计算
    • 重合点 (Overlap):用户笔迹 落在 标准答案区域内的点。
    • 越界点 (Mess):用户笔迹 落在 标准答案区域外的点。
  4. 判定
    • 如果 Overlop / 参考总面积 > thresholdCoverage
    • 并且 Mess / 用户总笔迹 < thresholdMess
    • 则判定为 通过

5. 使用示例

场景 A:在浏览器中使用 (原生 Canvas)

import { verifyPixels } from 'cn-signature-verifier/src/core.js';

// 1. 获取用户 Canvas 数据
const userCanvas = document.getElementById('userCanvas');
const userCtx = userCanvas.getContext('2d');
const userData = userCtx.getImageData(0, 0, 300, 150).data; // Uint8ClampedArray

// 2. 生成参考数据 (在内存中创建一个 Canvas)
const refCanvas = document.createElement('canvas');
refCanvas.width = 300;
refCanvas.height = 150;
const refCtx = refCanvas.getContext('2d');
// 绘制标准字
refCtx.font = 'bold 100px sans-serif';
refCtx.fillStyle = 'black';
refCtx.fillText('张三', 150, 75);
const refData = refCtx.getImageData(0, 0, 300, 150).data;

// 3. 调用验证
const result = verifyPixels(userData, refData, {
    thresholdCoverage: 0.1, // 稍微宽松一点
    thresholdMess: 0.6
});

if (result.pass) {
    console.log("验证成功!覆盖率:", result.score.coverage);
} else {
    console.error(result.message);
}

场景 B:在 Node.js 后端使用

如果你想在服务器端验证签名(例如用户上传了签名的 base64 图片),你需要使用 canvas 库。

npm install canvas
const { createCanvas, loadImage } = require('canvas');
const { verifyPixels } = require('cn-signature-verifier/src/core.js');

async function verifyOnServer(userImageBuffer, targetName) {
    const width = 300;
    const height = 150;

    // 1. 加载用户上传的图片
    const userCanvas = createCanvas(width, height);
    const userCtx = userCanvas.getContext('2d');
    const img = await loadImage(userImageBuffer);
    userCtx.drawImage(img, 0, 0, width, height);
    const userPixels = userCtx.getImageData(0, 0, width, height).data;

    // 2. 生成标准字图片
    const refCanvas = createCanvas(width, height);
    const refCtx = refCanvas.getContext('2d');
    refCtx.font = 'bold 100px "SimHei", sans-serif'; // 注意服务器要有对应字体
    refCtx.fillStyle = '#000';
    refCtx.textAlign = 'center';
    refCtx.textBaseline = 'middle';
    // 使用 strokeText 加粗,提高服务器端验证的容错率
    refCtx.lineWidth = 15;
    refCtx.strokeText(targetName, width / 2, height / 2);
    refCtx.fillText(targetName, width / 2, height / 2);
    
    const refPixels = refCtx.getImageData(0, 0, width, height).data;

    // 3. 验证
    // 注意:如果使用了加粗(lineWidth),建议降低 thresholdCoverage
    return verifyPixels(userPixels, refPixels, {
        thresholdCoverage: 0.08 
    });
}

6. 调优指南 (Troubleshooting)

当验证结果不符合预期时,请根据返回的 score 进行调整:

Q1: 用户写得很工整,但提示“请沿着背景字书写” (验证失败)

  • 原因:用户的笔画太细,或者背景字太细,导致重合面积不够;或者 thresholdCoverage 设得太高。
  • 解决
    1. 在 UI 上将背景字加粗 (lineWidth + strokeText)。
    2. options.thresholdCoverage 从默认的 0.12 降低到 0.08

Q2: 用户在旁边乱画圈,居然通过了?

  • 原因thresholdMess 设得太高,允许了过多的杂乱笔画。
  • 解决:将 options.thresholdMess 从默认的 0.65 降低到 0.5 或更低。

Q3: 用户还没写完(只写了一半),提示通过了?

  • 原因:用户写的那一半完美覆盖了背景字,导致 mess 很低,且 coverage 刚好过了及格线。
  • 解决:提高 options.thresholdCoverage

Q4: 我把背景字变得非常非常粗,结果很难通过?

  • 原因:分母(背景字面积)变大了,分子(重合面积)增加幅度不如分母大,导致覆盖率数值下降。
  • 解决字越粗,thresholdCoverage 应该越低。建议设为 0.06 - 0.08

7. 移动端体验优化指南

在手机端手写时,用户常反馈“明明写对了,但验证不通过”或“很难完全覆盖背景字”。这通常是因为:

  1. 手指遮挡:用户看不清笔尖落点。
  2. 背景字太细:标准字体的线条很细,用户稍微写偏一点就算“越界”或“未重合”。

解决方案:加粗字体 + 调整阈值

通过 Canvas 的 描边 (Stroke) 功能人为加粗背景字,并配合参数调整,可以显著提高通过率。

步骤 1:修改 UI 显示(让用户看到的字变粗)

在你的绘图初始化逻辑中(例如 initdrawBg 函数),使用 strokeText 来加粗文字。

// 1. 设置颜色
ctx.fillStyle = '#e0e0e0';
ctx.strokeStyle = '#e0e0e0'; // 描边颜色需与填充一致

// 2. 设置字体
ctx.font = 'bold 100px sans-serif'; // 使用粗体
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';

// 3. 【关键】设置描边宽度 (加粗程度)
// 推荐 15-20px,数值越大字越粗,容错率越高
ctx.lineWidth = 15; 
ctx.lineJoin = 'round'; // 圆润转角

// 4. 同时绘制描边和填充
ctx.strokeText(targetName, x, y); // 先描边
ctx.fillText(targetName, x, y);   // 后填充

步骤 2:修改验证逻辑(让程序比对的字也变粗)

注意: 验证时生成的“参考图”必须与用户看到的“背景图”粗细一致。

如果你使用的是封装好的 verifyWebverifyUniApp,可能需要修改适配器源码,或者确保传入的 font 足够粗。如果需要极高的容错率,建议直接调整核心校验参数。

步骤 3:调整覆盖率阈值 (thresholdCoverage)

这是最重要的一步。 当字体变粗后,字体的总像素面积(分母)会大幅增加。如果用户的笔画粗细不变,计算出的覆盖率数值会自然下降。

  • 未加粗时:及格线通常设为 0.12 (12%)。
  • 加粗后 (lineWidth=15):建议将及格线降低至 0.06 ~ 0.08

完整优化配置示例

const result = verifyPixels(userPixels, refPixels, {
    // 降低覆盖率要求,因为分母(背景字)变大了
    thresholdCoverage: 0.08, 
    
    // 越界率保持不变,或者稍微放宽
    thresholdMess: 0.65,
    
    // 必须确保 refPixels 生成时也使用了 strokeText 加粗,
    // 否则用户画在描边区域会被算作“越界”。
});

附:常见粗细设置参考

| 场景 | lineWidth 设置 | 推荐 thresholdCoverage | 体验描述 | | :--- | :--- | :--- | :--- | | PC 鼠标签字 | 0 (默认) | 0.12 | 精准,要求高。 | | 手机 - 适中 | 10 | 0.10 | 适合电容笔书写。 | | 手机 - 宽松 | 15 | 0.08 | 推荐。手指书写体验最佳,不容易误判。 | | 极度宽松 | 25 | 0.05 | 字非常肥大,用户几乎随便画画都能碰到字。 |