电子签名通俗来讲就是经过技术手段实如今电子文档上加载电子形式的签名,其做用相似于纸质合同上的手写签名或加盖的公章。虽然电子签名多年来合法性一直遭到质疑,但其在企业工做流审批、请柬、单据保全等场景应用普遍,最近的项目中就有这样一个手写签名并生成PDF文件的需求。html
实现思路<canvas className={styles.canvas} ref={canvasDom} width="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的区域渲染,于是图片会出现拉伸、变形等现象。算法
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() 方法定义的路径。默认颜色是黑色。
};
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;
});
注意:编程
clientX/clientY: 触摸位置距离当前body可视区域的x,y坐标; pageX/pageY: 对于整个页面来讲,触摸位置距离body左上角的x,y坐标,包括被scrollTop和scrollLeft的值;screenX/screenY: 触摸位置距离显示器左边和顶部的x,y距离。因此,在获取结束点坐标的时候,若是当前页面没有出现滚动条,使用clientY和pageY计算差异不大,若是页面比较长,出现了滚动条,那么就必需要使用pageY来计算。clientX同理,可是移动端一般横向滚动的场景很少,因此用clientX来计算便可。canvas
其实这个原理和微积分很类似,线段本质上就是由无穷多个小线段组成,宏观一点来看能够把线段当成一个个长度很小的小线段首尾相连构成。因此我一直以为编程编到最后就是考验一我的的数学能力,交并集、逻辑思惟、算法等都能看到数学的身影。最后生成签名以下:
浏览器
html2canvas是一款将HTML代码转换成Canvas的插件,所以须要用一个div包裹住须要打印的内容区域,得到这个dom节点。dom
html2Canvas(dom, {
allowTaint: true,
width: dom.offsetWidth, //设置获取到的canvas宽度
height: dom.offsetHeight, //设置获取到的canvas高度
x: 0, //页面在水平方向滚动的距离
y: 0, //页面在垂直方向滚动的距离
})
注意:此处须要设置width和height及x,y,不然当页面内容只有一页的时候没有问题,可是若页面内容有不少页的时候,就会出现生成的图片只有一小部分有内容的现象。问题就出如今这个配置参数上,若没有设置宽高,则默认只取当前视口的内容,丢弃掉其余超出当前视口的内容。设置打印参数:ide
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 < pageHeight) { //
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页数有不少的时候,会有这样的问题⏬ 能够看到,分页的时候从这段文字这里懒腰截断了。这显然不是咱们想要看到的效果,如何解决这个问题呢?????函数
能够在开发测试的时候预先在将要分页的地方插入一个padding,就是提早预留分页位置性能
对于这种状况,笔者尝试遍历要打印的dom节点的子节点,将每一页所能打印的dom节点高度累加,若超过了页面所能承载的最大高度,则将最后一个节点增长padding,打印完毕将样式还原。这种方法由于要计算每一个dom节点的高度,很是耗性能,也要求页面dom元素的颗粒度较细,不然会出现一个页面有大块空白,彻底没法模拟出word生成pdf的那种效果,因此就不展开讨论了。如如有读者有比较好的解放方案,欢迎不吝赐教,感谢~❤️测试