{"id":13446019,"url":"https://github.com/linjc/smooth-signature","last_synced_at":"2025-05-14T10:06:50.895Z","repository":{"id":37738853,"uuid":"420688044","full_name":"linjc/smooth-signature","owner":"linjc","description":"H5带笔锋手写签名，支持PC端和移动端，任何前端框架均可使用","archived":false,"fork":false,"pushed_at":"2024-10-18T03:51:11.000Z","size":445,"stargazers_count":932,"open_issues_count":7,"forks_count":105,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-04-03T14:07:40.304Z","etag":null,"topics":["canvas","javascript","react-signature","signature","typescript","vue-signature"],"latest_commit_sha":null,"homepage":"https://linjc.github.io/smooth-signature/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/linjc.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-10-24T13:11:13.000Z","updated_at":"2025-04-01T23:40:01.000Z","dependencies_parsed_at":"2024-05-30T10:15:06.676Z","dependency_job_id":"092810e9-5236-42f6-9818-02ec19568734","html_url":"https://github.com/linjc/smooth-signature","commit_stats":{"total_commits":42,"total_committers":2,"mean_commits":21.0,"dds":"0.16666666666666663","last_synced_commit":"56a7b8153ef0f96a1e4a99bd17b8d22cbba70100"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linjc%2Fsmooth-signature","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linjc%2Fsmooth-signature/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linjc%2Fsmooth-signature/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/linjc%2Fsmooth-signature/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/linjc","download_url":"https://codeload.github.com/linjc/smooth-signature/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248335580,"owners_count":21086622,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["canvas","javascript","react-signature","signature","typescript","vue-signature"],"created_at":"2024-07-31T05:00:43.752Z","updated_at":"2025-05-14T10:06:50.886Z","avatar_url":"https://github.com/linjc.png","language":"TypeScript","funding_links":[],"categories":["Libraries","Uncategorized","TypeScript"],"sub_categories":["Signature pad","Uncategorized"],"readme":"# smooth-signature 带笔锋手写签名\n\n- [Demo在线演示](https://linjc.github.io/smooth-signature/)  以下截图为手机端手写效果图\n\n![Demo](https://linjc.github.io/signature-demo.jpg)\n\n- [前言](#前言)\n- [安装](#安装)\n- [使用](#使用)\n    - [配置](#配置options)\n- [实现原理](#实现原理)\n    - [1、采集坐标](#1采集画笔经过的点坐标和时间)\n    - [2、计算速度](#2计算两点之间移动速度)\n    - [3、计算线宽](#3计算两点之间线的宽度)\n    - [4、画线](#4画线)\n- [快捷链接](#快捷链接)\n\n## 前言\n受疫情的影响，无纸化流程和电子合同开始普及，电子签名需求也不断增加，签名体验也在逐渐改善，从一开始简单的canvas画线，到追求线条丝滑圆润，再到要求和纸上写字一样的笔锋效果等等。网上不少现成开源的签名库，其中[signature_pad](https://github.com/szimek/signature_pad)笔锋效果实现比较好，但具体使用还是会发现有明显的锯齿感，于是利用工作之余，根据自身理解换了另一种方案实现了一套，同时也为小程序开发了一版，一起分享给有需要的同学。\n\n[mini-smooth-signature](https://github.com/linjc/mini-smooth-signature) 小程序版带笔锋手写签名，支持多平台小程序使用\n\n## 安装\n\n```bash\nnpm install smooth-signature\n# 或\nyarn add smooth-signature\n```\n\n或通过\u003cscript\u003e引用，全局变量 **window.SmoothSignature**\n\n```html\n\u003cscript src=\"https://unpkg.com/smooth-signature/dist/index.umd.min.js\" /\u003e\n```\n\n也可自行下载[smooth-signature.js](https://unpkg.com/browse/smooth-signature/dist/)到本地引用\n\n## 使用\n```html\n\u003cdiv\u003e\n    \u003ccanvas /\u003e\n\u003c/div\u003e\n```\n```js\nimport SmoothSignature from \"smooth-signature\";\n\nconst canvas = document.querySelector(\"canvas\");\nconst signature = new SmoothSignature(canvas);\n\n// 生成PNG\nconst url = signature.getPNG() // 或者 signature.toDataURL()\n\n// 生成JPG\nconst url = signature.getJPG() // 或者 signature.toDataURL('image/jpeg')\n\n// 清屏\nsignature.clear()\n\n// 撤销\nsignature.undo()\n\n// 重做\nsignature.redo()\n\n// 是否为空\nsignature.isEmpty()\n\n// 生成旋转后的新画布 -90/90/-180/180\nsignature.getRotateCanvas(90)\n```\n\n### 配置[options]\n所有配置项均是可选的\n```js\nconst signature = new SmoothSignature(canvas, {\n    width: 1000,\n    height: 600,\n    scale: 2,\n    minWidth: 4,\n    maxWidth: 10,\n    color: '#1890ff',\n    bgColor: '#efefef'\n});\n\n```\n**options.width**\n\n画布在页面实际渲染的宽度(px)\n* Type: `number`\n* Default：canvas.clientWidth || 320\n\n**options.height**\n\n画布在页面实际渲染的高度(px)\n* Type: `number`\n* Default：canvas.clientHeight || 200\n\n**options.scale**\n\n画布缩放，可用于提高清晰度\n* Type: `number`\n* Default：window.devicePixelRatio || 1\n\n**options.color**\n\n画笔颜色\n* Type: `string`\n* Default：black\n\n**options.bgColor**\n\n画布背景颜色，默认透明\n* Type: `string`\n* Default：\n\n**options.openSmooth**\n\n是否开启笔锋效果，默认开启\n* Type: `boolean`\n* Default：true\n\n**options.minWidth**\n\n画笔最小宽度(px)，开启笔锋时画笔最小宽度\n* Type: `number`\n* Default：2\n\n**options.maxWidth**\n\n画笔最大宽度(px)，开启笔锋时画笔最大宽度，或未开启笔锋时画笔正常宽度\n* Type: `number`\n* Default：6\n\n**options.minSpeed**\n\n画笔达到最小宽度所需最小速度(px/ms)，取值范围1.0-10.0，值越小，画笔越容易变细，笔锋效果会比较明显，可以自行调整查看效果，选出自己满意的值。\n* Type: `number`\n* Default：1.5\n\n**options.maxWidthDiffRate**\n\n相邻两线宽度增(减)量最大百分比，取值范围1-100，为了达到笔锋效果，画笔宽度会随画笔速度而改变，如果相邻两线宽度差太大，过渡效果就会很突兀，使用maxWidthDiffRate限制宽度差，让过渡效果更自然。可以自行调整查看效果，选出自己满意的值。\n\n* Type: `number`\n* Default：20\n\n**options.onStart**\n\n绘画开始回调函数\n\n* Type: `function`\n\n**options.onEnd**\n\n绘画结束回调函数\n\n* Type: `function`\n\n## 实现原理\n\n我们平时纸上写字，细看会发现笔画的粗细是不均匀的，这是写字过程中，笔的按压力度和移动速度不同而形成的。而在电脑手机浏览器上，虽然我们无法获取到触摸的压力，但可以通过画笔移动的速度来实现不均匀的笔画效果，让字体看起来和纸上写字一样有“笔锋”。下面介绍具体实现过程（以下展示代码只为方便理解，非最终实现代码）。\n\n#### 1、采集画笔经过的点坐标和时间\n通过监听画布move事件采集移动经过的点坐标，并记录当前时间，然后保存到points数组中。\n```js\nfunction onMove(event) {\n    const e = event.touches \u0026\u0026 event.touches[0] || event;\n    const rect = this.canvas.getBoundingClientRect();\n    const point = {\n        x: e.clientX - rect.left,\n        y: e.clientY - rect.top,\n        t: Date.now()\n    }\n    points.push(point);\n}\n```\n\n#### 2、计算两点之间移动速度\n通过两点坐标计算出两点距离，再除以时间差，即可得到移动速度。\n```js\nconst distance = Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2));\nconst speed = distance / (end.t - start.t);\n```\n\n#### 3、计算两点之间线的宽度\n得到两点间移动速度，接下来通过简单算法计算出线的宽度，其中maxWidth、minWidth、minSpeed为配置项\n```js\nconst addWidth = (maxWidth - minWidth) * speed / minSpeed;\nconst lineWidth = Math.min(Math.max(maxWidth - addWidth, minWidth), maxWidth);\n```\n另外，为了防止相邻两条线宽度差太大，而出现突兀的过渡效果，需要做下限制，其中maxWidthDiffRate为配置项，preLineWidth为上一条线的宽度\n```js\nconst rate = (lineWidth - preLineWidth) / preLineWidth;\nconst maxRate = maxWidthDiffRate / 100;\nif (Math.abs(rate) \u003e maxRate) {\n    const per = rate \u003e 0 ? maxRate : -maxRate;\n    lineWidth = preLineWidth * (1 + per);\n}\n```\n\n#### 4、画线\n现在已经知道每两点间线的宽度，接下来就是画线了。为了让线条看起来圆润以及线粗细过渡更自然，我把两点之间的线平均成三段，其中：\n1) 第一段（x0,y0 - x1,y1）线宽设置为当前线宽和上一条线宽的平均值lineWidth1 = (preLineWidth + lineWidth) / 2\n2) 第二段（x1,y1 - x2,y2）\n3) 第三段（x2,y2 - next_x0,next_y0）线宽设置为当前线宽和下一条线宽的平均值lineWidth3 = (nextLineWidth + lineWidth) / 2\n\n开始画线，先来看第一段线，因为第一段线和上一条线相交，为了保证两条线过渡比较圆润，采用二次贝塞尔曲线，起点为上一条线的第三段起点(pre_x2, pre_y2)\n```js\nctx.lineWidth = lineWidth1\nctx.beginPath();\nctx.moveTo(pre_x2, pre_y2);\nctx.quadraticCurveTo(x0, y0, x1, y1);\nctx.stroke();\n```\n\n第二段线为承接第一段和第三段的过渡线，由于第一段和第三段线宽有差异，所以第二段线使用梯形填充，让过渡效果更自然。\n```js\nctx.beginPath();\nctx.moveTo(point1.x, point1.y);\nctx.lineTo(point2.x, point2.y);\nctx.lineTo(point3.x, point3.y);\nctx.lineTo(point4.x, point4.y);\nctx.fill();\n```\n\n第三段等画下一条线时重复上述操作即可。\n\n## 快捷链接\n\n- [Example代码](./example)\n- [Github仓库](https://github.com/linjc/smooth-signature)\n- [Gitee仓库](https://gitee.com/l2j2c3/smooth-signature)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flinjc%2Fsmooth-signature","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flinjc%2Fsmooth-signature","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flinjc%2Fsmooth-signature/lists"}