前端基于React实现的PDF在线预览签名导出个人文章

最近做了一个简单版的电子合同,现在把这个项目中的一小部分功能,做了一个demo版本的PDF在线预览-签名-下载。

这个demo主要涉及三个环节,也就是PDF预览-签名-导出。

我遇到的,涉及pdfjs的坑有这么几个:

之前的项目有个功能点,签名状态可以保留。即合同需要两个人签署,如果一个人在移动端签名,放到PC端签名的位置会出现偏差;同理,PC端的签名放到移动端去看也会有差异。

分析问题:签名的数据,是会统一放到signPositionList里,上传数据库并进行保存。

//大概的签名数据信息interfaceISignPosition{id:string;x:number;//相对pdf渲染的偏移值xy:number;//偏移值yw:number;//签名宽h:number;//签名高signSrc:string;//签名图片isSelect:boolean;innerPdfIndex:number}typeSignList=ISignPosition[];上传的时候,会把签名的偏移值进行存储,预览的时候,获取签名的偏移值进行定位预览。

在看下签名偏移值x,y是怎么储存的:

//返回签名相对于pdfcanvas视口的偏移值exportconstgetTouchPosition=(e:any,scale:number)=>{constevent=getEvent(e);consttarget=event.target;constrect=target.getBoundingClientRect();constx=event.pageX-rect.left;consty=event.pageY-rect.top;//scale=canvas容器宽度/pdf渲染出来的canvas真实宽度return{x:x/scale,y:y/scale}}通过分析排查,问题的关键点就在于scale,主要原因是在PC和移动端两种不同环境下,pdfjs所渲染出来的容器宽度不一样,这里贴下当时手里的测试机,在两个环境中,签名的pdf宽高对比:

定位出问题以后,就开始分析解决,最终有了两种解决方案:

通过分析pdfjs源码可以得知,在pdfjs渲染时,除了当时浏览器宽度,其中有根据两个参数来控制的:

devicePixelRatio大家应该不陌生了,随着各种显示器设备的不断升级,这个值也在慢慢不断升级。值1表示经典96DPI(在某些平台上为76DPI)显示,而对于HiDPI/Retina显示屏则期望值为2。在异常低分辨率的显示器中,或更常见的是,当屏幕的像素深度比简单地将96或76DPI的标准分辨率提高一倍时,可能还会返回其他值。

maxCanvasPixels字面意义就是可以渲染的最高像素数,这个值在pdfjs源码里可以进行设置,如下图:

constMAX_CANVAS_PIXELS=_viewer_compatibility.viewerCompatibilityParams.maxCanvasPixels||16777216;classPDFPageView{constructor(options){constcontainer=options.container;constdefaultViewport=options.defaultViewport;this.maxCanvasPixels=options.maxCanvasPixels||MAX_CANVAS_PIXELS;...}如果想让pdfjs保持两端一致的渲染比例,可以对devicePixelRatio和maxCanvasPixels这两个值进行如下赋值:

window.devicePixelRatio=3;constpdfViewer=newpdfjsViewer.PDFViewer({container:containerRef.current,eventBus:eventBusRef.current,linkService,maxCanvasPixels:5242880,l10n:pdfjsViewer.NullL10n,useOnlyCssZoom:USE_ONLY_CSS_ZOOM,textLayerMode:TEXT_LAYER_MODE});这种方法比较hack,简单粗暴,改动最少,但是不同的手机设备预览可能会产生更多的风险。不建议使用。

算出上图中,签名偏移值在PC->H5之间的转换公式:

h5.x=h5.width*(pc.x/pc.width)h5.y=h5.height*(pc.y/pc.height)第一步,保存签名数据时,增加签名相对canvas容器的绝对百分比定位值:

//修改签名的数据信息,增加百分比偏移值interfaceISignPosition{id:string;xPercent:number;//相对canvas真实宽度的百分比位移yPercent:number;//相对canvas真实高度的百分比位移...}constrequestSignList=signList.map((sign)=>{return{...sign,xPercent:sign.x/trueWidth,yPercent:sign.y/trueHeight,}})第二步,在预览时,接口返回签名数据后,从百分比转成绝对值:

constsignList=apiSignList.map((sign)=>{return{...sign,x:trueWidth*xPercent,//真实偏移值/当前比例y:trueHeight*yPercent,}})这里只是贴些伪代码,本文里的这个项目并不涉及到需要h5&pc共同保存签名的功能。

eventBusRef.current.on("pagesloaded",function(){pdfViewerRef.current.currentScaleValue=DEFAULT_SCALE_VALUE;});但是如果你的pdf页数有点多,pdfjs出于性能问题考虑,会进行懒加载,如果这个时候,你想在pdfjs上层增加一个canvas渲染层,就不能监听到该事件就一起给加了,这个时候其实还有很多pdf并没有开始渲染。

pdf在浏览器中,应用canvas的地方有很多,如书写签名,签名交互以及签名的合成计算...

首先回顾一下,这个项目涉及canvas的有三处:

第一个canvas,是pdfjs渲染pdf生成的pdfCanvas,这里也就是pdf本身内容;

第二个canvas,是和pdfCanvas重叠生成的pdfSignCanvas,前边已经说过pdfSignCanvas是出于性能考虑的,主要是在执行签名的生成和交互时,这一层的canvas会频繁进行绘制,如果这个时候只有一层canvas,那么每次在pdfCanvas上开始拖拽签名进行交互时,将会频繁的重新绘制整个pdf的内容,这里的性能开销就非常大了。这一层canvas也是一开始的时候没有加,等到测试的时候,发现低端手机,各种花屏,卡顿,才给加上的。

canvas本身是没什么坑的,也就是api比较多,使用门槛相比其他库有点高...

关于canvas的一些使用技巧:先看一下,签名画板部分signPannelCanvas:

你可以在这个白色面板上进行签名/绘画,点击保存时,在将signPannelCanvas与signCanvas进行合并。以便将签名保存到pdf的画布中进行预览。如果按照传统做法,直接合并的话,那签名的区域会出现很多空白,毕竟你不可能把整个画布签满。

优化方案就是,我在两个canvas将要合并的时候,加了一个裁剪的过程:

整体优化流程:

这个项目的落地页是需要兼容PC和H5的,好在页面设计不是很复杂,没有太多需要特殊兼容的部分。

需要特殊处理的,就是签名画板的部分。因为手机的屏幕尺寸显然不如web宽敞,为了让用户有个较好的签名体验,需要将整个画布进行横向展示。

无论用户有没有开启手机的自动旋转,都要让用户横着手机进行签名。

为了考虑手机的自动旋转,需要加个监听事件,重新设置signPannelCanvas的宽高.

constgetCanvasSize=():Promise=>{returnnewPromise((resolve)=>{setTimeout(()=>{if(window.orientation===90){resolve({width:window.innerWidth,height:window.innerHeight-TITLE_MAP,})}else{resolve({width:window.innerWidth-TITLE_MAP,height:window.innerHeight,})}},500)})}consthandleCanvasSize=async()=>{constsize=awaitgetCanvasSize();updateCanvasSize(size);};useEffect(()=>{//加载签名模板,获取初始化大小handleCanvasSize();window.addEventListener("orientationchange",handleCanvasSize);return()=>{window.removeEventListener("orientationchange",handleCanvasSize);}},[]);最后一点,需要注意的是,移动端在强制横屏后,signPannelCanvas的宽高与手机的宽高是相反的。

所以在保存签名的时候,我们需要在把signPannelCanvas的签名翻转一下,才行。不然直接保存的话,签名也就跟着是反的。

//竖屏状态-翻转图像进行保存constctx=signPannelCanvas.getContext('2d');ctx.clearRect(0,0,width,height);canvas.width=clipSize.h;canvas.height=clipSize.w;constimg=createImage(imgData)img.onload=function(){//反向翻转绘制图片ctx.save();ctx.translate(clipSize.h/2,clipSize.w/2);ctx.rotate(-90*Math.PI/180);ctx.translate(-clipSize.h/2,-clipSize.w/2);ctx.drawImage(img,clipSize.h/2-img.width/2,clipSize.w/2-img.height/2);ctx.restore();//canvas有个重绘的过程,不能直接进行保存setTimeout(async()=>{//翻转后的正确签名constsignImage=canvas.toDataURL('image/png');addSignInCanvas(signImage,canvas.width,canvas.height);})}3.最后一个环节:pdf的导出/下载pdflib-支持原汁原味的pdf下载,这个库很方便,说到html->pdf转换,网上有很多方案,大都是将htmlToCanvas,imgToCanvas,toPdf。总之生成的pdf内部大都是图片,已经不是原汁原味的pdf了,这个库可以让你在原汁原味的pdf上额外添加图片,svg,文字等等...

这里比较折腾的一点是,如何把signCanvas上的图片,准确无误的添加到pdf文件指定的区域中。这里就直接贴代码了。和上面pc->h5的方案思路一致。

想熟悉canvas的同学,可以多了解一下。其中涉及canvas的内容,我上面没有讲太多,比如签名的绘制,交互,拖拽,变形等等...

顺带附下github地址,如果觉得有帮助,可以帮忙点个star,感谢~~

THE END
1.iPhone空白名制作教程,支持iOS15系统!ios果粉iphone苹果近日有不少果粉表示,他们升级到iOS 15 之后文件夹空白名已经失效了,桌面又变得乱七八糟。为此,小编(果粉之家)已经找来了新的代码,可以在iOS 15 及以上系统实现文件夹空白名效果。 话不多说,需要空白代码的小伙伴复制下方括号内的空白部分就可以直接进行粘贴使用了。 https://www.163.com/dy/article/GVMKDN3N05118P6D.html
2.王者荣耀名字生成器名字生成器在线工具这游戏空白名代码也是比较多玩家求的,琵琶网专区内每隔一段时间就会分享最新的空白名字代码,因为官方经常会更新,让一批代码失效,之前的空白代码也用不了,这种其实就是生成最新可用的空白代码。 3.特殊符号 有时候玩家看到特殊符号却不知道怎么打出来复制的,也可以领名字生成器,不过不是所有生成器都有这种功能,在线网页http://www.pipaw.com/wzry/516451.html
3.王者空白昵称助手app下载王者空白昵称助手安卓版下载v1.0王者空白昵称助手是一款很强大的手机游戏辅助工具,在这里用户可以在线设置空白名字,操作简单,通过这款软件,可以让玩家可以生成空白名,让你的昵称与众不同,还可以在线生成特殊的符号,会有专业的指引。软件功能强大,小巧不占内存,没有广告弹窗打扰,感兴趣的朋友可以下载 https://www.doyo.cn/app/215893.html
4.英雄联盟手游重复名生成器下载在线《英雄联盟手游重复名生成器》一款专业的游戏辅助app,可以根据自己的需要修改个性化名称,软件允许用户在不提示复制徽标的情况下快速修改名称,使您的游戏名称更加独特,并且空白名称也可以快速的修改,那还等什么呢,感兴趣的下载去体验吧。 软件简介 可以帮助每个人快速生成自己的名字,为更多使用英雄联盟的用户带来更多的https://app.3dmgame.com/mip/android/261353.html?ivk_sa=1024320u
5.Activiti的教学案例,Springboot+Activiti+Shiro项目代码Cancel Create saved search Sign in Sign up Reseting focus {{ message }} chutianmen / Activiti Public Notifications You must be signed in to change notification settings Fork 4 Star 5 Activiti的教学案例,Springboot+Activiti+Shiro项目代码 5 stars https://github.com/chutianmen/Activiti
6.空白代码生成器51CTO博客已为您找到关于空白代码生成器的相关内容,包含IT学习相关文档代码介绍、相关教程视频课程,以及空白代码生成器问答内容。更多空白代码生成器相关解答可以来51CTO博客参与分享和学习,帮助广大IT技术人实现成长和进步。https://blog.51cto.com/topic/blank-code-generator.html
7.请上面代码做什么处理或什么混淆编程语言变量混淆:代码中的变量名如i, s, a, o, c, u, f, h, p被混淆,它们可能代表原始代码中的不同变量。 函数混淆:extend, create, init, mixIn, clone这些函数名也被混淆了,它们的实际功能需要通过代码逻辑来推断。 动态代码生成:i函数的使用看起来像是在动态生成代码或变量名,这是混淆中常见的一种技术,使https://ask.csdn.net/questions/8122772
8.易燃易爆等场所雷电防护装置设计审核事项线上申报操作指南第六步:进入“易燃易爆等场所雷电防护装置设计审核”办事指南页面,查看基本信息、设定依据、受理条件、办理材料、办理流程等信息,根据“办理材料”要求,准备纸质和电子材料,自行下载空白表格及参照示例样表填写必要性材料。如下图: 3.3项目登记生成赋码(如已存在项目代码,则可忽略此步骤) https://www.danzhou.gov.cn/danzhou/ywdt/jrdz/202310/t20231019_3511851.html
9.html代码在线解析腾讯云开发者社区代码试图通过获取一个DOM节点并通过标记名从该节点获取元素来创建一个nodeList。简单的测试是显示nodeList.length。这是我在构建的应用程序中看到的一个较大问题的一个小样本。但我认为,在这里展示这一小段代码比向您展示我的整个应用程序更好。下面的代码可以在Chrome,Firefox,Opera以及更多浏览器上运行。但不是在MShttps://cloud.tencent.com/developer/information/html%E4%BB%A3%E7%A0%81%E5%9C%A8%E7%BA%BF%E8%A7%A3%E6%9E%90-ask
10.代码小工蚁英语单词列表生成软件说明书代码小工蚁英语单词列表生成软件是一款是集单词批量翻译、词形还原、单词随机乱序、各种格式的单词列表生成等功能于一体的英语学习辅助软件。 代码小工蚁英语单词列表生成软件采用Python 3.7编程语言开发,V2.0版使用了图形用户界面(GUI)。软件运行于Windows 7及以上环境,操作简单,使用方便。 https://www.jianshu.com/p/02c26c3495b3
11.go语言代码生成器codegenerator使用示例介绍Golang? 1、部分代码生成到输入包所在目录,例如 deepcopy-gen 生成器,也可以使用参数--output-file-base "zz_generated.deepcopy" 来定义输出文件名 ? 2、其它代码生成到 --output-package 指定的目录,例如 client-gen、informer-gen、lister-gen 等生成器 示例 接来下我们使用code-generator进行实战演示: 首先我https://www.jb51.net/article/248009.htm
12.网名生成器手机版分类:生活实用 大小:2.0M 语言:中文 版本:v5.21 官方版 时间:2017-08-09 09:21 星级: 官网:暂无 厂商: 平台:Android 标签:网名生成器安卓情侣软件网名助手独特的情侣网名在这里,你随便设置。应用截图应用介绍 特殊情侣网名生成器在线制作,想要什么样的情侣网名?这里可以在线制作哦!给你各种花样各种好玩的名字,https://www.qqtn.com/azsoft/246645.html
13.全民k歌空白爱称代码复制1、首先打开微信小程序,在搜索栏里面输入玩机工具具。2、其次找到空白昵称生成器,将空白昵称复制。3、https://ask.zol.com.cn/x/16051740.html
14.XML文档注释Doxygen:Doxygen通过一系列已记录的源文件生成在线文档浏览器(使用 HTML)或离线参考手册(使用 LaTeX)。 此外,还支持生成 RTF (MS Word)、PostScript、hyperlinked PDF、compressed HTML、DocBook 和 Unix 手册页形式的输出。 可以将 Doxygen 配置为从未记录的源文件中提取代码结构。 https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/xmldoc
15.网名生成器下载免费网名生成器有哪些网名生成器版本大全网名生成器软件帮助用户随机生成挑选各种有需要的名字,快速得到想要的效果,花样百出的网名资源任你挑选,还能对网名进行个性化处理添加一些可爱的花样和特效。如果你还在为找到一个好网名而发愁,这里的各种个性的网名能快速满足你的需要,让你在朋友圈里更具个性! 相关推荐:网名app http://www.downcc.com/k/wangmingscq/
16.王者荣耀名字生成器名字生成器在线工具这游戏空白名代码也是比较多玩家求的,琵琶网专区内每隔一段时间就会分享最新的空白名字代码,因为官方经常会更新,让一批代码失效,之前的空白代码也用不了,这种其实就是生成最新可用的空白代码。 3.特殊符号 有时候玩家看到特殊符号却不知道怎么打出来复制的,也可以领名字生成器,不过不是所有生成器都有这种功能,在线https://m.sohu.com/sa/412398055_100204787
17.免费代理ip获取验证器(网络自由行)v2.1免费版修复后台文件夹名修改后出现广告管理出错的问题 10、修复人才部分公司名不显示问题; 11、修复人才部分前台与后台职业类别提示名称不符; 12、修复人才首页不能自动生成; 13、修复人才小类不能删除问题; 14、修复人才职位信息不能按职位分类显示问题; 15、修复优化后台修改会员处编码问题; 16、修复供求二级页面相关类别https://www.iteye.com/resource/hxcs163-1817154