
简介这是一份面向前端开发者与移动端H5应用开发者的横屏手写签名解决方案专为手机横屏场景优化解决Web端签名功能在小屏设备上的适配与图片导出需求。资源包含jSignature核心插件及完整依赖体系涵盖27个JavaScript文件含jQuery、Vue基础支持、day.js日期处理等、15个CSS样式文件含index.css、body.css及第三方图标字体样式以及3个HTML主页面Sign_Sign.html、success.html等配合图片资源与字体文件woff/woff2/ttf/svg确保签名界面渲染一致。压缩包共69个文件大小2.98MB结构清晰、开箱即用无需额外配置即可本地运行并保存为PNG/JPG等多格式签名图。目前已有1073人学习下载适合需要快速集成横屏签名能力的项目开发、教育类电子签章原型验证或移动端表单增强实践。1. H5签名横屏jSignature源代码完整版可直接运行不是改个CSS就能横屏的黑匣子而是连Canvas重绘逻辑、触摸事件坐标映射、移动端横竖屏切换兜底都写死在源码里的实战组合包你试过在手机上用jSignature做电子签名横屏一转笔迹就歪斜、断点、甚至完全偏移出画布吗这不是浏览器兼容性玄学是jSignature原生不处理orientationchange事件、不重置Canvas坐标系、不拦截touchstart/touchmove原始坐标导致的硬伤。这份「H5签名横屏jSignature源代码完整版可直接运行」不是网上零散的CSS旋转hack或半截JS补丁而是把jSignature核心渲染层、事件监听器、Canvas尺寸重初始化、DPR适配、以及横屏状态下的坐标归一化映射全部重写并封装好的可执行工程。它专为需要在微信、企业微信、钉钉等内嵌WebView中稳定采集手写签名的业务场景而生——比如某高校在线报到系统、某公司远程入职协议签署模块。如果你正卡在“签名能画但横屏就废”“安卓机笔迹抖动严重”“iOS Safari下签名区域错位”这类问题上这份资源就是你不用再翻十页Stack Overflow、不用自己啃jSignature源码就能落地的完整解法。2. 横屏签名的核心矛盾为什么原生jSignature在移动端横屏时必然失效2.1 Canvas坐标系与设备方向的底层撕裂从screen.width/screen.height到canvas.width/canvas.height的三重错位jSignature默认初始化时会读取容器DOM元素的offsetWidth/offsetHeight并据此设置canvas的width和height属性值注意这是Canvas的绘图缓冲区分辨率非CSS显示尺寸。当用户横屏时window.innerWidth突变为原innerHeight但jSignature既不监听orientationchange事件也不主动调用resizeCanvas()更不会重新计算scaleX/scaleY缩放因子。结果就是Canvas缓冲区仍维持竖屏时的宽高如375×667但CSS样式被强制拉伸至667×375touchstart获取的clientX/clientY坐标按横屏后的新视口计算却直接喂给旧尺寸Canvas的2D上下文最终笔迹在视觉上被严重挤压、偏移甚至超出画布边界——这不是UI错位是坐标空间彻底崩塌。提示canvas.width/canvas.height决定像素密度和绘图精度canvas.style.width/canvas.style.height仅控制显示缩放。二者必须同步重置否则DPR设备像素比适配失效签名线条发虚。2.2 移动端触摸事件坐标的陷阱touches[0].clientXvsgetBoundingClientRect().left原生jSignature在touchmove中直接使用e.touches[0].clientX - canvas.getBoundingClientRect().left计算X坐标。这在竖屏下成立但横屏时getBoundingClientRect()返回的left/top值受CSS Transform影响而clientX仍是屏幕绝对坐标。更致命的是部分安卓WebView尤其旧版UC内核在横屏后getBoundingClientRect()缓存未刷新导致left值仍为竖屏旧值坐标计算直接偏移数百像素。本资源的解法是放弃getBoundingClientRect()改用canvas.offsetLeft/canvas.offsetTopwindow.pageXOffset/window.pageYOffset组合并在每次orientationchange后强制清空坐标缓存。关键代码如下// 在jSignature初始化后注入的横屏适配钩子 function initOrientationHandler($sig) { let lastOrientation window.orientation || (window.matchMedia window.matchMedia((orientation: portrait)).matches ? 0 : 90); function updateCanvasSize() { const canvas $sig.find(canvas).get(0); const container $sig.get(0); const dpr window.devicePixelRatio || 1; // 1. 重置Canvas缓冲区尺寸关键 canvas.width Math.floor(container.offsetWidth * dpr); canvas.height Math.floor(container.offsetHeight * dpr); // 2. 重置CSS显示尺寸保持1:1缩放 $(canvas).css({ width: container.offsetWidth px, height: container.offsetHeight px }); // 3. 重置2D上下文缩放确保笔迹粗细一致 const ctx canvas.getContext(2d); ctx.scale(dpr, dpr); // 4. 强制jSignature重载画布触发内部clearCanvas $sig.jSignature(reset); } // 监听横竖屏切换兼容iOS/Android window.addEventListener(orientationchange, () { setTimeout(updateCanvasSize, 100); // 防止布局未完成 }); // 兜底监听页面尺寸变化防微信WebView等不触发orientationchange的场景 window.addEventListener(resize, () { const currentOrientation window.orientation || (window.matchMedia window.matchMedia((orientation: portrait)).matches ? 0 : 90); if (Math.abs(currentOrientation - lastOrientation) 45) { lastOrientation currentOrientation; setTimeout(updateCanvasSize, 150); } }); }这段代码在$sig.jSignature()初始化后立即调用它不依赖任何第三方库纯原生实现。setTimeout延迟是为了等待浏览器完成视口重排Layout避免offsetWidth读取到旧值。dpr缩放保证高分屏下线条清晰ctx.scale(dpr, dpr)是jSignature官方推荐的DPR适配方式而非简单放大CSS。2.3 jSignature事件监听器的重绑定为什么bind后还要unbind原生jSignature在init时将touchstart/mousemove等事件绑定到Canvas元素上但横屏后Canvas DOM节点未销毁事件监听器依然存在。问题在于事件回调函数闭包中捕获的canvas引用、offsetLeft/offsetTop值、甚至scaleX/scaleY参数都是竖屏时的快照。若不主动解绑并重建监听器新坐标计算逻辑永远无法生效。本资源在updateCanvasSize()中增加强制解绑步骤// 在updateCanvasSize()函数末尾追加 function rebindEventHandlers($sig) { const canvas $sig.find(canvas).get(0); const $canvas $(canvas); // 解绑所有jSignature相关事件避免重复绑定 $canvas.off(touchstart mousedown); $canvas.off(touchmove mousemove); $canvas.off(touchend mouseup); // 重新绑定使用更新后的坐标计算函数 $canvas.on(touchstart mousedown, function(e) { const touch e.type touchstart ? e.touches[0] : e; const x touch.clientX - canvas.offsetLeft window.pageXOffset; const y touch.clientY - canvas.offsetTop window.pageYOffset; // ... 后续jSignature标准处理逻辑 }); }注意off()必须指定事件类型不能用off()全清否则会误删其他业务代码绑定的事件。rebindEventHandlers()在每次横屏重置后调用确保监听器始终使用最新Canvas状态。3. 完整版源码结构解析从index.html到js/signature.js每个文件都解决一个具体问题3.1 项目根目录文件清单与职责分工共7个核心文件文件名类型核心职责是否可删除index.htmlHTML模板包含横屏适配meta、签名容器DOM、基础CSS、jSignature初始化脚本❌ 不可删入口css/signature.cssCSS样式表定义.signature-pad容器宽高100%、禁用用户选中、横屏媒体查询覆盖⚠️ 可删需自行重写CSSjs/jSignature.min.js原生库jSignature v2.1.2压缩版已打补丁❌ 不可删依赖js/signature.js主业务脚本封装横屏初始化、事件重绑定、DPR适配、导出逻辑❌ 不可删核心js/utils.js工具函数getTouchPosition()坐标归一化、isLandscape()方向判断、debounce()防抖⚠️ 可删功能可合并demo.png示例图片横屏签名效果截图供测试验证✅ 可删README.md说明文档快速启动指南、参数配置说明、常见问题索引✅ 可删注意js/jSignature.min.js并非官网原版而是本资源作者基于v2.1.2源码手动patch的版本——主要修改了_initEvents()和_drawLine()两个方法增加了this._orientationState状态标记和_resetCanvasOnOrientation()钩子。若你坚持用官网原版需自行在signature.js中补全这些patch逻辑。3.2signature.js核心逻辑链从初始化到导出的五步闭环整个签名流程被封装为SignaturePad类调用链清晰// 1. 初始化在DOM ready后执行 $(document).ready(function() { const pad new SignaturePad(#signature-pad, { minWidth: 1, // 最小线宽px maxWidth: 3, // 最大线宽px横屏时自动乘以DPR penColor: #000, // 笔迹颜色 backgroundColor: rgba(0,0,0,0), // 透明背景避免导出白底 throttle: 16, // 事件节流ms平衡流畅性与性能 dotSize: 1 // 点击时的圆点半径 }); // 2. 绑定横屏适配器关键 pad.initOrientationHandler(); // 3. 绑定导出按钮事件 $(#export-btn).click(function() { const data pad.toDataURL(); // 默认PNG格式 $(#export-result).val(data); }); // 4. 绑定清空按钮 $(#clear-btn).click(function() { pad.clear(); }); // 5. 绑定签名完成回调业务侧处理 pad.on(endStroke, function() { console.log(签名结束总点数, pad.getData().length); }); });SignaturePad构造函数接收配置对象其中throttle: 16是血泪经验——低于16ms安卓低端机易卡顿高于32ms笔迹断点明显。backgroundColor: rgba(0,0,0,0)必须设为透明否则toDataURL()导出的PNG带白底与业务系统背景色冲突。3.3utils.js中的坐标归一化函数解决跨设备触摸偏移的终极方案getTouchPosition()是本资源最硬核的工具函数它绕过getBoundingClientRect()的缺陷用canvas.offsetLeft/offsetTop结合scrollX/scrollY计算绝对坐标// utils.js function getTouchPosition(canvas, touch) { const rect canvas.getBoundingClientRect(); const scaleX canvas.width / rect.width; // 水平缩放因子 const scaleY canvas.height / rect.height; // 垂直缩放因子 // 关键用offsetLeft/offsetTop替代getBoundingClientRect().left/top // 因为offset*是相对于父元素的稳定值不受CSS transform影响 return { x: (touch.clientX - canvas.offsetLeft - window.pageXOffset) * scaleX, y: (touch.clientY - canvas.offsetTop - window.pageYOffset) * scaleY }; } // 在signature.js的touchstart事件中调用 $canvas.on(touchstart, function(e) { e.preventDefault(); const touch e.touches[0]; const pos getTouchPosition(canvas, touch); // 获得Canvas坐标系下的精确点 // 后续传给jSignature的_drawLine方法... });该函数在小米、华为、iPhone各代机型实测通过scaleX/scaleY确保即使Canvas被CSS缩放坐标也能精准映射到缓冲区像素。e.preventDefault()阻止页面滚动是移动端签名体验的底线。4. 避坑横屏签名开发中踩过的5个真实坑每一条都来自某次线上事故回溯4.1 现象横屏后第一次签名笔迹正常第二次开始严重偏移原因jSignature(reset)只清空画布但未重置内部_lastPoint缓存。横屏后_lastPoint仍为竖屏坐标导致第二笔的起始点计算错误。解决在updateCanvasSize()中调用$sig.jSignature(setData, [])清空数据栈再调用$sig.jSignature(reset)双保险。4.2 现象iPhone X/XS等刘海屏横屏时签名区域顶部被状态栏遮挡原因viewportmeta未设置viewport-fitcoverSafari在横屏时自动收缩视口避开刘海。解决在index.html的head中添加meta nameviewport contentwidthdevice-width, initial-scale1.0, maximum-scale1.0, user-scalableno, viewport-fitcover4.3 现象安卓微信内置浏览器横屏后orientationchange事件完全不触发原因微信Android版尤其7.0.20以下对orientationchange支持极差且window.orientation恒为0。解决弃用orientationchange改用resize事件matchMedia兜底const mediaQuery window.matchMedia((orientation: landscape)); mediaQuery.addListener(function(e) { if (e.matches) { // 进入横屏 updateCanvasSize(); } else { // 进入竖屏 updateCanvasSize(); } });4.4 现象签名导出为PNG后文件体积暴增5倍从20KB到100KB原因横屏后Canvas缓冲区尺寸变大如从375×667→667×375但toDataURL()未指定质量参数生成未压缩PNG。解决导出时强制JPEG压缩// 替换原生toDataURL() pad.toDataURL function(type, encoderOptions) { return this._canvas.toDataURL(image/jpeg, 0.8); // 80%质量体积降60% };4.5 现象某款国产安卓平板横屏签名时笔迹呈阶梯状锯齿原因该设备WebView的touchmove事件触发频率极低≈10HzjSignature默认采样率跟不上。解决在SignaturePad构造参数中提高throttle容忍度并启用smooth插值new SignaturePad(#signature-pad, { throttle: 32, smooth: true, // 启用贝塞尔曲线平滑 minDistance: 5 // 两点间距离小于5px才合并减少噪点 });5. 进阶技巧如何用这份源码支撑「多签名区域动态尺寸离线缓存」生产级需求5.1 多签名区域的实例管理避免全局变量污染的工厂模式当一个页面需同时渲染「甲方签字」「乙方签字」「骑缝章」三个独立签名区时不能简单多次new SignaturePad()——jSignature内部使用全局_instances数组管理多个实例会互相干扰。正确做法是封装工厂函数隔离每个实例的事件监听器和状态// signature-factory.js class SignatureFactory { static create(containerId, options {}) { const $container $(#${containerId}); const $canvas $(canvas/canvas); $container.append($canvas); // 为每个实例生成唯一事件命名空间 const namespace signature.${Date.now()}.${Math.random().toString(36).substr(2, 9)}; const instance new SignaturePad($canvas.get(0), { ...options, // 重写事件绑定使用命名空间避免冲突 _initEvents: function() { this._$canvas.on(touchstart.${namespace}, this._onStart.bind(this)); this._$canvas.on(touchmove.${namespace}, this._onMove.bind(this)); } }); // 暴露销毁方法清理事件 instance.destroy function() { this._$canvas.off(.${namespace}); $canvas.remove(); }; return instance; } } // 使用 const signA SignatureFactory.create(sign-area-a, { penColor: #0055ff }); const signB SignatureFactory.create(sign-area-b, { penColor: #ff5500 });此模式让每个签名区完全独立destroy()可随时卸载适合SPA应用中路由切换时的内存清理。5.2 动态尺寸签名响应式容器下的实时Canvas重绘策略当签名容器被CSS Flex/Grid动态拉伸时如左右分栏右侧签名区宽度随窗口变化需监听容器尺寸而非窗口。本资源提供observeContainerResize()方法// 在signature.js中扩展 SignaturePad.prototype.observeContainerResize function() { const resizeObserver new ResizeObserver(entries { for (let entry of entries) { if (entry.target this._$canvas.parent().get(0)) { this._resetCanvas(); // 内部调用updateCanvasSize() } } }); resizeObserver.observe(this._$canvas.parent().get(0)); }; // 初始化后调用 pad.observeContainerResize();ResizeObserver是现代浏览器标准API比resize事件更精准且无性能损耗。若需兼容IE11可降级为setInterval轮询offsetWidth。5.3 离线签名缓存利用localStorage持久化未提交的签名数据用户填写表单中途退出再次进入时应恢复未提交的签名。本资源在endStroke回调中自动缓存pad.on(endStroke, function() { const data pad.getData(); // 以页面URL容器ID为key避免不同表单冲突 const key ${location.pathname}__${pad._$canvas.parent().attr(id)}; localStorage.setItem(key, JSON.stringify(data)); }); // 页面加载时恢复 $(document).ready(function() { const key ${location.pathname}__${pad._$canvas.parent().attr(id)}; const cached localStorage.getItem(key); if (cached) { try { const data JSON.parse(cached); pad.fromData(data); // 自动绘制缓存数据 } catch (e) { localStorage.removeItem(key); // 解析失败则清除脏数据 } } });提示getData()返回的是点坐标数组如[[x1,y1],[x2,y2]]体积小、序列化安全。切勿缓存toDataURL()的Base64字符串体积过大且易触发localStorage 5MB限制。从那以后我每次接到「横屏签名」需求第一件事就是把这份源码解压到/src/signature/目录然后在index.html里改三行ID、两行颜色、一行导出路径——它已经替我扛过了所有坐标映射、DPR适配、事件重绑的脏活。真正的难点从来不是“怎么画”而是“怎么让画出来的每一笔在任何时间、任何设备、任何朝向都稳稳落在用户想点的那个位置”。希望帮到你。本文还有配套的精品资源点击获取