app canvas渲染后图片黑色_H5 基于 canvas 实现电子签名并生成PDF文档

(给前端大全加星标,提升前端技能)

转自:coyota666

https://juejin.cn/post/6901273585428463624

前言

电子签名通俗来说就是通过技术手段实现在电子文档上加载电子形式的签名,其作用类似于纸质合同上的手写签名或加盖的公章。虽然电子签名多年来合法性一直遭到质疑,但其在企业工作流审批、请柬、单据保全等场景应用广泛,最近的项目中就有这样一个手写签名并生成PDF文件的需求。

实现思路

  1. 使用canvas来实现手写签名的功能,然后将canvas转化为图片,贴在签名的位置;
  2. 将整个需要生成文档的dom区域使用html2canvas插件转成一张大图;
  3. 使用JsPDF插件将上述图片生成PDF文档;
  4. 对于文件内容较多的情况,需要合理选择分页位置;

生成签名

1. 在tsx中定义canvas画布

 "350" height="150" />

注意:Canvas的宽高必须要使用内联样式定义,这是因为Canvas标签有自己的默认宽高300px×150px。它内联样式定义的width和height是绘画区域(画布)实际宽度和高度,绘制的图形都是在这个上面。如果在style外链文件中定义其width和height,那么这个width和height是Canvas在浏览器中被渲染的高度和宽度。如果Canvas中没有直接定义width和height没或值不正确,就会被设置成默认值{width:300px,height:150px}。所以,如果你在style中外链文件中设置了canvas {width: 200px; height: 200px;},却没有直接在canvas上定义画布宽高,那么此时你输出canvas.height 值依旧为150,canvas.width值依旧为300。也就是一块150×300的画布在200×200的区域渲染,因而图片会出现拉伸、变形等现象。

2. 定义签名函数

 const writing = (
    beginX: number,
    beginY: number,
    stopX: number,
    stopY: number,
    ctx: any,
  ) => {
    ctx.beginPath();  // 开启一条新路径
    ctx.globalAlpha = 1;  // 设置图片的透明度
    ctx.lineWidth = 3;  // 设置线宽
    ctx.strokeStyle = 'red';  // 设置路径颜色
    ctx.moveTo(beginX, beginY);  // 从(beginX, beginY)这个坐标点开始画图
    ctx.lineTo(stopX, stopY);  // 定义从(beginX, beginY)到(stopX, stopY)的线条(该方法不会创建线条)
    ctx.closePath();  // 创建该条路径
    ctx.stroke();  // 实际地绘制出通过 moveTo() 和 lineTo() 方法定义的路径。默认颜色是黑色。
  };

3. 注册监听事件

    let beginX: number, beginY: number;
    const canvas: HTMLCanvasElement = canvasDom.current;
    const ctx = canvas.getContext('2d');
    ctx.fillStyle = '#fff';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    canvas.addEventListener('touchstart', function(event: any) {
      event.preventDefault(); // 阻止在canvas画布上签名的时候页面跟着滚动
      beginX = event.touches[0].clientX - this.offsetLeft; 
      beginY = event.touches[0].pageY - this.offsetTop;
    });
    canvas.addEventListener('touchmove', (event: any) => {
      event.preventDefault(); // 阻止在canvas画布上签名的时候页面跟着滚动
      event = event.touches[0];let stopX = event.clientX - canvas.offsetLeft;let stopY = event.pageY - canvas.offsetTop;
      writing(beginX, beginY, stopX, stopY, ctx);
      beginX = stopX; // 这一步很关键,需要不断更新起点,否则画出来的是射线簇
      beginY = stopY;
    });

注意

  1. 在注册“touchstart”和“touchmove”事件时,需要阻止默认事件,否则页面会跟着手势上下滑动。
  2. 移动端的每个触摸事件对象中都包括了touches这个属性,它用于描述位于屏幕上的所有手指的一个列表,获取当前事件对象我们习惯性的使用event = event.touches[0],而在PC端则不需要这么操作。
  3. offsetLeft值跟offsetTop值跟父级元素没关系,而是跟其上一级的定位元素(除position:static外的所有定位如fixed,relative,absolute元素)有关系。若上一级定位元素都没有除position:staice外的定位,则这个偏移量是相对于body而言的。
  4. 需要理清移动端事件对象的几个属性,⏬dbce9e3f36dffc1cf9d1fc2985a07b4f.png

clientX/clientY: 触摸位置距离当前body可视区域的x,y坐标;
pageX/pageY: 对于整个页面来说,触摸位置距离body左上角的x,y坐标,包括被scrollTop和scrollLeft的值;
screenX/screenY: 触摸位置距离显示器左边和顶部的x,y距离。
所以,在获取结束点坐标的时候,如果当前页面没有出现滚动条,使用clientY和pageY计算差别不大,如果页面比较长,出现了滚动条,那么就必须要使用pageY来计算。clientX同理,但是移动端通常横向滚动的场景不多,所以用clientX来计算即可。

  1. 在签名(touchmove)这个动作过程中,我们需要不断的更新起点位置,否则画出来是这样?

2b7372e477c5f6d7706dde229fb9d8bb.png其实这个原理和微积分很相似,线段本质上就是由无穷多个小线段组成,宏观一点来看可以把线段当成一个个长度很小的小线段首尾相连构成。所以我一直觉得编程编到最后就是考验一个人的数学能力,交并集、逻辑思维、算法等都能看到数学的身影。最后生成签名如下:cde6368d52c0f634db641eea737c9ed5.png

生成PDF文档

html2canvas是一款将HTML代码转换成Canvas的插件,因此需要用一个div包裹住需要打印的内容区域,获得这个dom节点。

html2Canvas(dom, {
    allowTaint: true,
    width: dom.offsetWidth, //设置获取到的canvas宽度
    height: dom.offsetHeight, //设置获取到的canvas高度
    x: 0, //页面在水平方向滚动的距离
    y: 0, //页面在垂直方向滚动的距离
   })

注意:此处需要设置width和height及x,y,否则当页面内容只有一页的时候没有问题,但是若页面内容有很多页的时候,就会出现生成的图片只有一小部分有内容的现象。问题就出现在这个配置参数上,若没有设置宽高,则默认只取当前视口的内容,丢弃掉其他超出当前视口的内容。
设置打印参数:

const print = () => {let dom: HTMLElement = pdfDom.current;
    html2Canvas(dom, {
      allowTaint: true,
      width: dom.offsetWidth, //设置获取到的canvas宽度
      height: dom.offsetHeight, //设置获取到的canvas高度
      x: 0, //页面在水平方向滚动的距离
      y: 0, //页面在垂直方向滚动的距离
    }).then((canvas: HTMLCanvasElement) => {let canvasWidth = canvas.width;let canvasHeight = canvas.height;let pageHeight = (canvasWidth / 592.28) * 841.89; // 一页A4 pdf能显示的canvas高度let imgWidth = 595.28; // 设置图片宽度和A4纸宽度相等let imgHeight = (592.28 / canvasWidth) * canvasHeight;//等比例换算成A4纸的高度let totalHeight = imgHeight; // 需要打印的图片总高度,初始状态和图片高度相等let pageData = canvas.toDataURL('image/png', 1.0);let PDF = new JsPDF('p', 'pt', 'a4', true);if (totalHeight         PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight); // 从顶部开始打印
      } else {let top = 0;   // 打印初始区域while (totalHeight > 0) {
          PDF.addImage(pageData, 'JPEG', 0, top, imgWidth, imgHeight);  // 从图片顶部往下top位置开始打印
          totalHeight -= pageHeight;
          top -= 841.89;if (totalHeight > 0) {
            PDF.addPage();
          }
        }
      }
      PDF.save('test.pdf');
    });
  };

选择分页位置

按照上述步骤生成了一份PDF文档,但是当PDF页数有很多的时候,会有这样的问题⏬da01ef62e530f5b8c190c3062aa757a1.png可以看到,分页的时候从这段文字这里懒腰截断了。这显然不是我们想要看到的效果,如何解决这个问题呢??

  • PDF文档页数较少的情况

可以在开发测试的时候预先在将要分页的地方插入一个padding,就是提前预留分页位置

  • PDF文档页数较多

对于这种情况,笔者尝试遍历要打印的dom节点的子节点,将每一页所能打印的dom节点高度累加,若超过了页面所能承载的最大高度,则将最后一个节点增加padding,打印完毕将样式还原。这种方法因为要计算每个dom节点的高度,非常耗性能,也要求页面dom元素的颗粒度较细,否则会出现一个页面有大块空白,完全无法模拟出word生成pdf的那种效果,所以就不展开讨论了。如若有读者有比较好的解放方案,欢迎不吝赐教,感谢~❤️

推荐阅读  点击标题可跳转

1、烧脑!JS+Canvas 带你体验「偶消奇不消」的智商挑战

2、canvas 中普通动效与粒子动效的实现

3、基于 HTML5 Canvas 的交互式地铁线路图

觉得本文对你有帮助?请分享给更多人

推荐关注「前端大全」,提升前端技能

9ab387e10935d32789c480fc14cee3d9.png

点赞和在看就是最大的支持❤️

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/550718.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端为什么说github很重要_私域流量|为什么说私域流量很重要?

前面几篇文章我们分析了私域流量的概念,现在我们来分析一下私域流量的好处。为什么说私域流量很重要呢?首先,私域流量可以把营销成本降低,较快地获取用户反馈。之前在公域流量上进行的交易,都是你买我卖,你…

idea的jsp如何显示语法高亮_如何啃下Python学习中的三块硬骨头?

Python 根式字【51CTO.com快译】众所周知,作为一门开源的高级编程语言,Python的用途十分广泛。它可以被用于不同的应用场景中,包括:开发基于桌面和Web的应用程序,分析访问数据,开发后端网站和人工智能等。虽…

几种经典排序算法

文章转载自:指月小筑 原文链接:链接

Eclipse配置自动补齐键为alt+/

在顶栏找到window,选择下拉栏中的Preferences 在Preferences的搜索框输入keys,在右边的候选区域找到Content Assist,在Binding中输入Alt/ Apply and Close即可

公共界面_公共建筑东营市档案馆能耗计量分析系统

摘要:本文介绍东营市档案馆能耗管理系统,采用智能电力仪表采集配电现场的各种电参量,仪表就地组网后通过现场总线通讯并远传至后台,通过Acrel-5000型建筑能耗监测系统实现配电回路用电的监测分析。关键词:大型公共建筑…

IDEA、pycharm白嫖攻略

本方法仅适用于高校大学生 JetBrains系列下的idea、pycharm优秀的代码提示让人爱不释手,但是相比较eclipse,idea的巨大缺点就是:要收费。 我看到身边的许多同学为了使用idea、pycharm而到处搜索激活码或者破解软件 ,但是激活码还是…

com.alibaba.easyexcel导出指定的列_使用Python导入导出Excel表格

这篇文章的目的是讲解使用python导入导出Excel表格,目前还不涉及数据处理,主要实现为数据的展示。第一步,准备材料python3.8的安装包一个Excel表格第二步,安装软件python可以采用默认安装的方式,安装完之后把python和p…

2021-08-10 HDFS Web报错Couldn‘t preview the file.

在查看文件内容时&#xff0c;在红框位置出现报错“Couldn’t preview the file” 解决方法 第一步&#xff1a;修改hdfs-site.xml&#xff0c;添加配置信息 <property><name>dfs.webhdfs.enabled</name><value>true</value> </property&…

2021-08-10 maven配置阿里云仓库

方式一&#xff1a;全局配置 可以添加阿里云的镜像到maven的setting.xml配置中&#xff0c;这样就不需要每次在pom中&#xff0c;添加镜像仓库的配置&#xff0c;在mirrors节点下面添加子节点&#xff1a; <mirror><id>nexus-aliyun</id><mirrorOf>ce…

大橙子_橙子皮养花太棒了,酸性大,肥力足,比花肥强10倍

随着社会的不断发展&#xff0c;现在越来越多的人都喜欢在家里养花。在家里养花&#xff0c;可以装点居室&#xff0c;让我们家里更温馨漂亮&#xff0c;而且我们在养花的时候也能培养我们的兴趣&#xff0c;也有利于我们的身心健康。其实养花最主要的就是要补充充足的养分&…

adaptivitypara设置选0还是1_喝牛奶,选全脂还是低脂?家里人能不能喝同1种牛奶?...

年龄大的人&#xff0c;还记得&#xff0c;当年&#xff0c;牛奶是限量供应的&#xff0c;家里有婴儿或病人&#xff0c;才能得到1张卡&#xff0c;每天按时在街边等候&#xff0c;凭卡可以买到半斤掺水的牛奶。牛奶&#xff0c;被誉为接近完善的食品&#xff0c;其中所含的蛋白…

Centos8修改mysql密码

第一步&#xff1a;修改配置文件免密码登录mysql 进入文件&#xff1a;vi /etc/my.cnf文件末尾添加skip-grant-tables重启mysql 第二步: 免密码登录mysql 输入mysql登录&#xff1a; 进入数据库&#xff0c;输入&#xff1a; use mysql&#xff1b;查看root用户信息&#xf…

aop的实现原理_非Spring管理Bean如何添加AOP呢?

前几天有个朋友问了一个问题&#xff0c;觉得可以给大家分享一下。问题如下图归其根本这是个历史项目&#xff0c;里面有很多的类并没有交给spring管理&#xff0c;但现在需要统一添加日志。面对这样的问题&#xff0c;其实只要了解AOP的原理&#xff0c;就会有多种方法。AOP都…

什么从什么写短句_2020抖音文案短句:爱情、励志、伤感合集,值得收藏!

视频火不火&#xff0c;一半看文案&#xff01;想知道爆款视频的文案都是什么吗&#xff1f;想知道爆款文案的套路吗&#xff1f;今天&#xff0c;我就给大家整理了近期抖音爆款短视频的文案——爱情、励志、伤感短句&#xff0c;句句深入人心&#xff0c;赶紧来看看吧&#xf…

插入数据的时候出现错误:Error during job, obtaining debugging information…

插入数据的时候出现错误:Error during job, obtaining debugging information… 原因&#xff1a; Javaf堆内存不足 解决办法 修改为本地模式 set hive.exec.mode.local.autotrue;

::在sql语句中是什么写法_不懂就问:SQL 语句中 where 条件后 写上1=1 是什么意思...

程序员在编程过程中&#xff0c;经常会在代码中使用到“where 11”&#xff0c;这是为什么呢&#xff1f;SQL注入初次看到这种写法的同学肯定很纳闷&#xff0c;加不加where 11&#xff0c;查询不都一样吗&#xff1f;例如&#xff1a;select * from customers; 与 select * fr…

django 集成个推_持续集成CircleCI vs Travis CI vs Jenkins

Continuous Integration. CircleCI vs Travis CI vs Jenkins​hackernoon.com持续集成(CI)的定义及其主要目标持续集成(CI)是一种软件开发实践&#xff0c;它基于将代码频繁集成到共享代码仓中。 然后通过自动构建(automated build)验证每个签入(Check-In)。持续集成(CI)的主要…

战双帕弥什自抽号怎么使用_战双帕弥什新S冰露怎么玩《战双帕弥什》新S冰露玩法技巧...

战双帕弥什新S冰露怎么玩呢&#xff0c;新的S构造体更新之后正式登场&#xff0c;不少玩家都对他不太了解&#xff0c;接下来就让小编给大家带来《战双帕弥什》新S冰露玩法技巧介绍。《战双帕弥什》新S冰露玩法技巧介绍 冰露技能分为两种形式&#xff0c;一种是极寒形态&#x…

log4j:WARN No appenders could be found for logger (org.apache.ibatis.logging.LogFactory). log4j:WARN

1. 警告信息 log4j:WARN No appenders could be found for logger (org.apache.ibatis.logging.LogFactory). log4j:WARN Please initialize the log4j system properly. log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. 2. 解决方法 在…

ppt讲解中的过渡_PPT黑科技,只用一张图做出3D动画

我一直在探索PPT中平滑切换的应用。这篇文章感觉算是探索到头了。之前的文章中&#xff0c;曾经提到过平滑切换可以让3D模型动起来。但是在实际应用中&#xff0c;能够供PPT使用的3D素材数量有限&#xff0c;难以准确契合我们的需求。后来我发现&#xff0c;只需要1张普通图片&…