H5横屏签名方案:jSignature实现与避坑指南
简介一套可直接运行的H5横屏手写签名完整源码基于jSignature插件构建面向需要快速在移动端集成手写签名功能的前端开发者。核心功能包括手机横屏适配、触控签名板交互、图片格式可选以及将签名保存为本地图片整个过程无需后端参与。资源打包为rar压缩包共69个文件、约2.98MB其中27个js文件承担jSignature插件、jQuery及页面交互逻辑15个css用于横屏布局与视觉样式3个html构成可直接打开的示例页面另配多套字体资源ttf、woff、woff2、svg、eot、otf与图片、psd设计源文件目录结构清晰方便按需替换或二次开发。已有1073人学习下载这套方案无论是作为H5签名功能起步模板还是作为理解jSignature封装思路、横屏事件适配和图片导出细节的参考样例都有实用价值也可直接引用到真实项目中。1. 横屏签名为啥非用 jSignature这套 H5 方案解决的是手感问题在移动端做电子签名“横屏”是一个经常被忽略但体验又很重要的场景。银行业务、法务审批、物流签收这类需求里用户更习惯把手机横过来像握笔一样在屏幕中下部书写因为横向宽高比更接近真实纸质表单。H5 签名横屏 jSignature 源代码指的就是基于 jSignature 库在网页中生成一块可手写画布并以横屏布局为主线的完整前端实现。这个方案的核心价值是纯前端、无需原生控件签名结果既能导出 PNG 图片也能以 SVG 矢量数据存储为后续验真和合同归档留了口子。本文读者是正在做 H5 业务、被触摸签名折磨过的前端工程师读完你不但能跑通最小可用版本还能避开我在真实项目里踩过的那些坑。2. 横屏签名的底层逻辑jSignature 的工作原理与选型判断2.1 jSignature 用矢量轨迹而不是像素堆叠来记录笔迹先别急着复制代码得搞清楚 jSignature 为什么在横屏签名里这么稳。它做的事情是把手指或触控笔经过的坐标点按时间序列采集再用贝塞尔曲线拟合出平滑轨迹最后以“每一笔的控制点数组”形式保存而不是直接保存一张照片。举例来说你写下一个“一”字jSignature 记录的不是一行像素点颜色而是一个包含起笔点、落笔点、曲线控制点坐标的数组。我见过不少新手以为签名控件和画板一样每次 mousemove 就往 canvas 上画个圆点那样存下来的根本不是签名是一堆离散的像素操作记录后续要改颜色、改线宽、合成到 PDF 上全都得重画。jSignature 默认的 getData 返回的数据格式长这样// jSignature 默认 getData() 返回一个数组 // 每个元素是 x,y 字符串代表一个笔迹控制点 [ 2,4, 3,5, 4,5, 5,6 ]这套数据本质上是 SVG 路径指令的变体。它带来的直接好处是文件体积极小一个几千个采样点的签名在内存里通常只有几十 KB。连续签几十个客户页面不会卡死。第二个好处是可二次编辑因为存的是矢量路径导出时改背景色、改笔迹粗细、放大缩小都不会失真。做合同归档的业务后端经常需要把白底黑字的签名合成到 PDF 页面上如果一开始存的是位图换个纸张背景就得重新抠图非常痛苦。2.2 横屏带来的宽高比变化决定了签名“像不像真笔迹”竖屏签名最大的问题是画布宽高比。竖屏状态下画布往往是 9:16写出来的字会被纵向拉伸签名看起来又窄又长用户在手机上觉得还行导出后放到 16:9 的电脑屏幕上看就变形了。横屏之后画布比例变成 16:9 左右更接近用户在纸质文件上签名的视觉宽度这也是业务方坚持要做横屏的根本原因。一个很容易忽略的事实是jSignature 本身不关心屏幕方向它只负责在给定宽高的 canvas 里捕获坐标。所以横屏的关键工作不在 jSignature 内部而在外层布局与视口适配让 canvas 的 CSS 宽度尽可能贴近屏幕横屏可用宽度高度按签名需要的比例收缩。我一般会把签名区做成“从屏幕顶部往下占约 55% 高度”的横条既避免手指误触屏幕底部的手势区也给上方按钮位留出空间。2.3 选型前先确认三个边界条件把 jSignature 塞进项目之前我习惯先过三个边界条件避免做到一半翻车跑在什么 WebView 内核上jSignature 是 jQuery 插件依赖 jQuery 的事件体系和选择器。如果业务要兼容四五年前的旧 WebView 内核建议先做一次真机回归。有些老旧内核的 canvas 绘制性能很差连续快速划动时笔迹会断线。要不要压感笔迹jSignature 默认不读取触摸压力值它把每个触点当成统一半径来渲染。如果产品经理说“我想要重按出粗笔、轻写出细笔”那得自己扩展 touch 事件的 force 字段这部分最后一章会讲。离线还是在线签名数据导出格式要提前和后端约定。如果还需要离线暂存草稿优先选 SVG 矢量格式因为它的体积小放进 localStorage 也不心疼。3. 完整源代码怎么组织一套能直接运行的横屏签名页面这一章是可以直接抄作业的部分。一个能真正跑起来的“完整版”核心入口就一个 index.html外加本地引用的两个库文件。考虑到很多企业内部环境禁止外链 CDN我这边以本地文件为主要方案CDN 方式写在备注里二选一即可。3.1 文件结构、引入顺序与版本选择我习惯在项目根目录这样安排h5-signature/ ├── index.html ├── css/ │ └── sign.css ├── js/ │ ├── jquery.min.js │ └── jSignature.min.js引入顺序有讲究必须先把 jQuery 加载完再加载 jSignature。jSignature 在插件定义阶段会直接检测 jQuery 是否存在如果顺序反了控制台会立刻抛错后面的画布根本没机会创建。script srcjs/jquery.min.js/script script srcjs/jSignature.min.js/script我一般用 jQuery 1.12.x 搭配 jSignature 2.x这个组合覆盖了从安卓老旧 WebView 到新版 iOS 的绝大多数环境。不用追新版本jQuery 3.x 在部分老机器上的兼容反而不如 1.12 稳定签名控件这种老牌插件追求的是稳而不是新。3.2 HTML 骨架与横屏锁定 CSS页面的 DOM 结构是签名交互的核心我分了三层外层 phone 容器模拟横向视口中间放提示栏和按钮最里面才是签名画布容器。分层的意义在于即使浏览器不允许强制横屏页面 CSS 也能强制以横向比例展示签名区。div idphone div classsign-header span请横屏签名/span button typebutton idbtnClear重签/button /div div idsignArea/div button typebutton idbtnSubmit classbtn-confirm确认签名/button /div对应 CSS 有几个关键点#phone { width: 100vw; height: 56.25vw; /* 16:9 横向比例避免竖屏下画布被拉高 */ max-height: 100vh; margin: 0 auto; box-sizing: border-box; position: relative; background: #f7f8fa; } #signArea { width: 100%; height: calc(100% - 96px); /* 上方按钮区 下方确认按钮的高度预留 */ box-sizing: border-box; touch-action: none; /* 关键禁止浏览器默认触摸滚动 */ background: #fff; }这里有个很容易忽略的细节touch-action: none。如果没有这条 CSS浏览器会用默认触摸行为处理页面滚动和缩放手指在画布上划动时会被系统判定为滚动手势签名笔画会断成一截截的。我接手过的 H5 签名项目里有三分之一“画不上线”的问题其实都是少了这一行。3.3 初始化参数与清空、确认的基础逻辑接下来是 JavaScript 初始化。下面这份代码可以直接粘到一个空页面里调试$(document).ready(function () { var $signArea $(#signArea); // jSignature 初始化宽度高度走 100%让插件自己去量父容器 $signArea.jSignature({ width: 100%, height: 100%, color: #2b2b2b, background-color: transparent, lineWidth: 2, undoButton: false }); // 重签清空画布 $(#btnClear).on(click, function () { $signArea.jSignature(clear); }); // 确认签名导出两种格式后续接后端时按需取用 $(#btnSubmit).on(click, function () { var svgData $signArea.jSignature(getData, image/svgxml); var pngData $signArea.jSignature(getData, image/png); console.log(svg , svgData); console.log(png , pngData); }); });参数说明width: 100%和height: 100%不写死像素值让 jSignature 初始化时去读取父容器尺寸。这里最容易被坑如果父容器高度是 0或者还没渲染完成就初始化canvas 高度会变成 0整个签名区直接消失。color: #2b2b2b签名笔迹颜色。别用纯黑#000纯黑在扫描件上会产生过度对比看起来像打印机喷墨反而不真实。background-color: transparent画布背景设透明方便后续合到合同底图上。如果你希望有白色纸面效果改成#ffffff再由外层 div 加边框。lineWidth: 2默认线宽在 2K 屏的安卓机上会显得偏细我通常会在初始化前读取 devicePixelRatio把线宽乘上去。如果你直接改大这个值老的 iPhone 上笔迹又会粗得像马克笔。undoButton: falsejSignature 内置的撤销按钮布局很丑融入不了业务 UI我都是关掉它自己用“重签”按钮代替。别忘了在 HTML 头部加 viewport meta禁止双击缩放meta nameviewport contentwidthdevice-width, initial-scale1, maximum-scale1, user-scalableno不加这条的话用户快速连点两下页面会放大画布坐标全部错位。4. 签名数据导出、回显与上传接后端前必须吃透的三种格式签名控件做完之后难点不在画而在把签名的结果安全送到后端。这一章把三种数据流说透PNG 位图、SVG 矢量、回显时导入。这一步做不好前端画得再顺后端也没法把签名落进合同。4.1 导出 PNG 位图清晰度、尺寸与透明底三个核心参数最常见的做法是把签名区导出成 PNG 交给后端存成图片文件。jSignature 的 getData 方法接受 MIME 类型作为参数// 导出 PNG base64 字符串 var pngBase64 $signArea.jSignature(getData, image/png); // 结果形如data:image/png;base64,iVBORw0KGgo...这块最常见的坑是“图片发虚”。jSignature 初始化时创建的 canvas默认逻辑尺寸等于容器 CSS 尺寸不会自动适配屏幕的 devicePixelRatio。安卓机上常见 1.5、2 甚至 3.75 的像素比如果画布逻辑像素只有 CSS 像素的一半导出图片在电脑上看就会模糊。我的习惯是初始化前先把画布逻辑尺寸乘以 dprvar dpr window.devicePixelRatio || 1; var areaWidth $(#signArea).width(); var areaHeight $(#signArea).height(); $(#signArea).jSignature({ width: areaWidth * dpr, height: areaHeight * dpr, lineWidth: 2 * dpr, color: #2b2b2b, background-color: transparent });但这样引入一个新问题canvas 固有尺寸变大后CSS 布局上显示也会跟着变大。解决办法是初始化后再加一条 CSS强制 canvas 显示尺寸回退到父容器 100%#signArea canvas { width: 100% !important; height: 100% !important; touch-action: none; }这样画布的物理像素是 CSS 显示尺寸的 dpr 倍视觉上仍然铺满容器导出图片清晰度才能保住。这个方案在安卓和 iOS 的 WebView 里都验证过是相对最稳的一招。4.2 导出 SVG 矢量合同归档的最佳数据形态PNG 适合展示SVG 才是 jSignature 最有价值的输出。SVG 存的是笔画的矢量路径文件体积小放大不失真还能从数据层面做字迹比对。用法如下// 导出 SVG 字符串 var svgData $signArea.jSignature(getData, image/svgxml); // 伪示例返回的是完整 SVG XML 字符串 // svg xmlnshttp://www.w3.org/2000/svg width... height... // path dM2,3 C2,3 3,4 4,4 Z fillnone stroke#2b2b2b stroke-width2/ // /svg真正与后端对接时我建议传 SVG 字符串而不是 PNG。理由很简单SVG 字符串通常只有 1 到 5KB比同样的 PNG 小一个数量级后端拿 SVG 可以直接内嵌进 PDF 或转成图片不需要额外引入压缩处理。这里提醒一点jSignature 导出的 SVG 保留了画布的绝对坐标。如果用户在签名过程中旋转过手机导致画布尺寸变化SVG 的坐标系也会跟着变。所以回显时必须在同一个尺寸的画布上做否则笔迹会错位。这也是横屏签名比竖屏更容易出问题的深层原因。4.3 回显之前必须做的清理操作以及向后端提交的最小实现回显场景很常见用户签完名进入确认页或者编辑时重新加载需要把之前保存的 SVG 重新画回画布。jSignature 提供 setData 方法// 回显 SVG 签名 var savedSvg ...; // 后端返回的 SVG 字符串 $signArea.empty(); // 必须先把旧画布清掉 $signArea.jSignature(setData, [image/svgxml, savedSvg]);这里有三个连环坑。第一setData 之前不加 empty 的话新数据会叠加在旧笔迹上形成重影。第二参数必须是数组格式[image/svgxml, svgString]少一个字段或者顺序反了都会静默失败控制台还不报错我当年在这里花了两小时才找到原因。第三要在页面渲染完成后再调 setData如果你在 DOM 还没布局好时就执行画布宽度为 0导入会失败且没有提示。表单提交时我一般把 SVG 和 PNG 一起放进 FormDatavar svgData $signArea.jSignature(getData, image/svgxml); var canvas $signArea.find(canvas)[0]; canvas.toBlob(function (blob) { var fd new FormData(); fd.append(signatureSvg, svgData); fd.append(signaturePng, blob, sign.png); fd.append(customerId, $(#customerId).val()); $.ajax({ url: /api/sign/upload, method: POST, data: fd, processData: false, contentType: false, success: function (res) { alert(签名保存成功); } }); }, image/png);注意canvas.toBlob在部分老 WebView 里不存在。如果必须兼容老机型先用getData(image/png)拿到 base64再手动转换var base64 $signArea.jSignature(getData, image/png); fd.append(signaturePngBase64, base64);后端拿 base64 解码也不复杂只是多一步而已。5. 横屏签名避坑清单5 个高频故障现象、原因与修复下面这 5 条是我在多个 H5 签名项目里反复踩过的坑属于“只要你做横屏签名几乎一定会遇见”的级别。每条按“现象 → 原因 → 解决”写清楚你可以直接对照排查。5.1 画布白屏但画不出笔迹触摸层被透明遮罩挡住现象页面加载正常白色画布显示出来了手指划上去什么痕迹都没有控制台也没有任何报错。原因最常见的元凶是某个透明遮罩层覆盖在 canvas 上方。比如“请横屏”提示框淡出后留下的透明 div或者某个 loading 浮层容器没有销毁。jSignature 监听的是 canvas 自身的 mousedown、mousemove、mouseup 以及对应 touch 事件这些事件不会穿透遮盖元素。解决第一步浏览器开发者工具里检查元素确认是哪个节点压在 canvas 上面第二步如果是浮层残留在画布初始化结束后把浮层 display:none第三步给 canvas 加一条position: relative; z-index: 10保证它待在兄弟节点之上。另外如果你使用了某些弹层组件注意它的 mask 是否默认全屏覆盖。5.2 笔迹跟手指错位CSS 缩放与 Canvas 固有尺寸不一致现象手指按在屏幕左侧笔迹却出现在偏中间的位置越靠近屏幕边缘偏差越明显。原因jSignature 初始化时测量的 canvas 固有尺寸和 CSS 渲染尺寸不一致。常见于横屏适配时给 canvas 写了width: 100%; height: 100%但 canvas 元素的 width/height 属性仍是初始化时的某个固定值。触摸事件的坐标被 jSignature 按固有尺寸处理视觉上 CSS 又把画布拉伸了坐标自然对不上。解决不要把固定像素写死在 HTML 里统一用初始化时的实际容器尺寸。最稳妥的方式是让 canvas 的固有宽高等于容器宽高乘以 dpr再用 CSS 强制显示尺寸退回 100%。如果画布尺寸必须在某个时机改变比如窗口旋转改完后要重新初始化 jSignature让它重新测量不要复用旧实例。5.3 横屏锁定失败WebView 策略和用户习惯的博弈现象点击“开始签名”后页面没有旋转成横屏或者一开始是横的手机轻微倾斜又变回竖屏。原因浏览器的横屏锁定 API 并非处处可用。screen.orientation.lock只在部分安卓 WebView 和较新的 iOS Safari 里生效很多小程序容器或 App 内置 WebView 直接禁用了这个 API。而 CSS 的media (orientation: landscape)只是样式适配并不会强制物理屏幕旋转。解决务实做法是“软锁横屏”。进入签名页时提示用户把手机横过来同时页面 CSS 保证即使当前处于竖屏状态签名区也按 16:9 比例渲染用户一转手机就能直接用。对于支持锁屏 API 的浏览器可以尝试调用if (screen.orientation screen.orientation.lock) { screen.orientation.lock(landscape-primary).catch(function () { // 不支持就别强求走软锁方案 }); }注意 catch 必须写上因为很多浏览器会抛 Promise 异常不处理的话控制台会报红。但我不把它作为唯一手段真正的兜底是布局上的横屏自适应。5.4 导出图片模糊像素比、最大画布尺寸和手机型号的三角平衡现象签名在手机上看很清晰导出 PNG 后放到电脑上查看边缘发虚、锯齿明显。原因上一章提过canvas 逻辑尺寸没有按 dpr 放大。生产环境里还有一个隐藏细节部分安卓机上报的 dpr 是 2但系统缩放后实际渲染像素比更高导致导出尺寸依然不够。反过来如果你为了清晰度无限放大 dpr又可能撞上 canvas 的最大尺寸限制。解决初始化前读取 devicePixelRatio把画布宽高乘以 dpr线宽也乘以 dprCSS 显示尺寸保持容器逻辑尺寸。另外要把 dpr 封顶在 3 左右。我测试过从 2 倍提升到 3 倍清晰度提升明显从 3 倍到 4 倍文件变大了肉眼清晰度几乎没区别反而是部分机型 canvas 超过 4096 宽度后直接绘制中断。5.5 旋转屏幕后签名消失或位移resize 时机没有处理现象用户正签着名系统通知栏一拉横竖屏切换回到页面发现笔迹全没了或者笔迹偏移了一大截。原因jSignature 在初始化时把画布尺寸和事件绑定都确定了。窗口 resize 后canvas 的固有尺寸不会自动改变绘图上下文里的坐标系却可能已经错乱某些版本的 jSignature 在检测到容器尺寸变化后会直接把画布内容清空。解决两个策略一起上。第一个签名页尽量锁定布局方向不要在签名过程中允许切换方向。第二个监听 resize 事件一旦方向变化且签名已经存在先 getData 保存当前数据等尺寸稳定后再 setData 还原window.addEventListener(resize, function () { var curSvg $signArea.jSignature(getData, image/svgxml); // 延迟到方向动画完成之后再重置画布 setTimeout(function () { $signArea.empty(); $signArea.jSignature(setData, [image/svgxml, curSvg]); }, 300); });这套“后悔药”写法防的是那一瞬间的 resize 把画布清空实测能救回绝大多数签名现场但最好还是从入口处就尽量避免方向切换。6. 进阶技巧让签名笔迹更像真实手写同时保住导出清晰度标准 jSignature 用统一线宽画每条笔画所以签出来的名字看起来像用同一支马克笔写的。真实手写时笔尖和屏幕接触的力度不同笔画的粗细会有变化。jSignature 不默认读取压力值但我们可以自己扩展。做法不复杂在初始化完成后监听 canvas 上的 touchmove 事件读出 touch 对象的 force 字段var lastForce 1; $(#signArea).on(touchmove, function (e) { var touch e.originalEvent.touches[0]; if (touch.force touch.force 0) { lastForce touch.force; } });拿到力值之后一个讨巧的做法是每次 touchend 后对最后一段笔迹做一次粗细归一化力气大的段落保留较粗的 stroke力气小的段落变细。但 jSignature 本身没有公开“改某一段线宽”的 API所以现实中更多是在导出 SVG 时对 path 的 stroke-width 做二次加工而不是在绘制中实时改。换句话说压力感这件事在展示层做比在采集层做更容易控制。第二个重点是清晰度。签名只是前端交互的一小步真正决定业务成败的是后端拿到的图片能不能直接上合同。我在一次上线前做过批量测试同一支触控笔在 iOS 和两台不同分辨率的安卓设备上各签了二十次导出 PNG 后统计有效像素占比。结论是dpr 放大到 2 到 3 倍清晰度提升最明显继续放大到 4 倍体积增加清晰度收益几乎为零。所以现在凡是新项目我都把 dpr 封顶写死为 3。另外一个调试习惯也分享给你不要全程依赖真机远程调试真机手感虽然最准但效率太低。我会在本地临时做一个隐藏按钮直接在当前页面输出签名数据到控制台再配合浏览器开发者工具的移动端模拟器做快速循环验证。把“画一笔 → 看数据 → 改参数 → 再画一笔”这个循环缩短到一分钟内能大幅减少无效沟通。如果你最后遇到“明明代码没变这次画布却空白了”的情况先看控制台有没有报错再看是不是新增的弹层把画布盖住了这两步能定位九成以上问题。希望帮到你。本文还有配套的精品资源点击获取