<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          H5基于Canvas實現(xiàn)電子簽名并生成PDF文檔

          共 4786字,需瀏覽 10分鐘

           ·

          2020-12-25 18:19

          原文:https://juejin.cn/post/6901273585428463624


          前言

          電子簽名通俗來說就是通過技術(shù)手段實現(xiàn)在電子文檔上加載電子形式的簽名,其作用類似于紙質(zhì)合同上的手寫簽名或加蓋的公章。雖然電子簽名多年來合法性一直遭到質(zhì)疑,但其在企業(yè)工作流審批、請柬、單據(jù)保全等場景應用廣泛,最近的項目中就有這樣一個手寫簽名并生成PDF文件的需求。

          實現(xiàn)思路


            1. 使用canvas來實現(xiàn)手寫簽名的功能,然后將canvas轉(zhuǎn)化為圖片,貼在簽名的位置;
            2. 將整個需要生成文檔的dom區(qū)域使用html2canvas插件轉(zhuǎn)成一張大圖;
            3. 使用JsPDF插件將上述圖片生成PDF文檔;
            4. 對于文件內(nèi)容較多的情況,需要合理選擇分頁位置;

          生成簽名

          1. 在tsx中定義canvas畫布

          ?"350"?height="150"?/>

          注意:Canvas的寬高必須要使用內(nèi)聯(lián)樣式定義,這是因為Canvas標簽有自己的默認寬高300px×150px。它內(nèi)聯(lián)樣式定義的width和height是繪畫區(qū)域(畫布)實際寬度和高度,繪制的圖形都是在這個上面。如果在style外鏈文件中定義其width和height,那么這個width和height是Canvas在瀏覽器中被渲染的高度和寬度。如果Canvas中沒有直接定義width和height沒或值不正確,就會被設(shè)置成默認值{width:300px,height:150px}。所以,如果你在style中外鏈文件中設(shè)置了canvas {width: 200px; height: 200px;},卻沒有直接在canvas上定義畫布寬高,那么此時你輸出canvas.height 值依舊為150,canvas.width值依舊為300。也就是一塊150×300的畫布在200×200的區(qū)域渲染,因而圖片會出現(xiàn)拉伸、變形等現(xiàn)象。

          2. 定義簽名函數(shù)

          ?const?writing?=?(
          ????beginX:?number,
          ????beginY:?number,
          ????stopX:?number,
          ????stopY:?number,
          ????ctx:?any,
          ??)?=>?{
          ????ctx.beginPath();??//?開啟一條新路徑
          ????ctx.globalAlpha?=?1;??//?設(shè)置圖片的透明度
          ????ctx.lineWidth?=?3;??//?設(shè)置線寬
          ????ctx.strokeStyle?=?'red';??//?設(shè)置路徑顏色
          ????ctx.moveTo(beginX,?beginY);??//?從(beginX,?beginY)這個坐標點開始畫圖
          ????ctx.lineTo(stopX,?stopY);??//?定義從(beginX,?beginY)到(stopX,?stopY)的線條(該方法不會創(chuàng)建線條)
          ????ctx.closePath();??//?創(chuàng)建該條路徑
          ??? ctx.stroke();??//?實際地繪制出通過 moveTo()?和 lineTo()?方法定義的路徑。默認顏色是黑色。
          ??};

          3. 注冊監(jiān)聽事件

          ????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;?//?這一步很關(guān)鍵,需要不斷更新起點,否則畫出來的是射線簇
          ??????beginY?=?stopY;
          ????});

          注意

            1. 在注冊“touchstart”和“touchmove”事件時,需要阻止默認事件,否則頁面會跟著手勢上下滑動。
            2. 移動端的每個觸摸事件對象中都包括了touches這個屬性,它用于描述位于屏幕上的所有手指的一個列表,獲取當前事件對象我們習慣性的使用event = event.touches[0],而在PC端則不需要這么操作。
            3. offsetLeft值跟offsetTop值跟父級元素沒關(guān)系,而是跟其上一級的定位元素(除position:static外的所有定位如fixed,relative,absolute元素)有關(guān)系。若上一級定位元素都沒有除position:staice外的定位,則這個偏移量是相對于body而言的。
            4. 需要理清移動端事件對象的幾個屬性,?

          clientX/clientY: 觸摸位置距離當前body可視區(qū)域的x,y坐標;
          pageX/pageY: 對于整個頁面來說,觸摸位置距離body左上角的x,y坐標,包括被scrollTop和scrollLeft的值;
          screenX/screenY: 觸摸位置距離顯示器左邊和頂部的x,y距離。
          所以,在獲取結(jié)束點坐標的時候,如果當前頁面沒有出現(xiàn)滾動條,使用clientY和pageY計算差別不大,如果頁面比較長,出現(xiàn)了滾動條,那么就必須要使用pageY來計算。clientX同理,但是移動端通常橫向滾動的場景不多,所以用clientX來計算即可。


            1. 在簽名(touchmove)這個動作過程中,我們需要不斷的更新起點位置,否則畫出來是這樣?

          其實這個原理和微積分很相似,線段本質(zhì)上就是由無窮多個小線段組成,宏觀一點來看可以把線段當成一個個長度很小的小線段首尾相連構(gòu)成。所以我一直覺得編程編到最后就是考驗一個人的數(shù)學能力,交并集、邏輯思維、算法等都能看到數(shù)學的身影。最后生成簽名如下:

          生成PDF文檔

          html2canvas是一款將HTML代碼轉(zhuǎn)換成Canvas的插件,因此需要用一個div包裹住需要打印的內(nèi)容區(qū)域,獲得這個dom節(jié)點。

          html2Canvas(dom,?{
          ????allowTaint:?true,
          ????width:?dom.offsetWidth,?//設(shè)置獲取到的canvas寬度
          ????height:?dom.offsetHeight,?//設(shè)置獲取到的canvas高度
          ????x:?0,?//頁面在水平方向滾動的距離
          ????y:?0,?//頁面在垂直方向滾動的距離
          ???})

          注意:此處需要設(shè)置width和height及x,y,否則當頁面內(nèi)容只有一頁的時候沒有問題,但是若頁面內(nèi)容有很多頁的時候,就會出現(xiàn)生成的圖片只有一小部分有內(nèi)容的現(xiàn)象。問題就出現(xiàn)在這個配置參數(shù)上,若沒有設(shè)置寬高,則默認只取當前視口的內(nèi)容,丟棄掉其他超出當前視口的內(nèi)容。
          設(shè)置打印參數(shù):

          const?print?=?()?=>?{
          ????let?dom:?HTMLElement?=?pdfDom.current;
          ????html2Canvas(dom,?{
          ??????allowTaint:?true,
          ??????width:?dom.offsetWidth,?//設(shè)置獲取到的canvas寬度
          ??????height:?dom.offsetHeight,?//設(shè)置獲取到的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;?//?設(shè)置圖片寬度和A4紙寬度相等
          ??????let?imgHeight?=?(592.28?/?canvasWidth)?*?canvasHeight;//等比例換算成A4紙的高度
          ??????let?totalHeight?=?imgHeight;?//?需要打印的圖片總高度,初始狀態(tài)和圖片高度相等
          ??????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;???//?打印初始區(qū)域
          ????????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頁數(shù)有很多的時候,會有這樣的問題?可以看到,分頁的時候從這段文字這里懶腰截斷了。這顯然不是我們想要看到的效果,如何解決這個問題呢??

          • PDF文檔頁數(shù)較少的情況

          可以在開發(fā)測試的時候預先在將要分頁的地方插入一個padding,就是提前預留分頁位置

          • PDF文檔頁數(shù)較多

          對于這種情況,筆者嘗試遍歷要打印的dom節(jié)點的子節(jié)點,將每一頁所能打印的dom節(jié)點高度累加,若超過了頁面所能承載的最大高度,則將最后一個節(jié)點增加padding,打印完畢將樣式還原。這種方法因為要計算每個dom節(jié)點的高度,非常耗性能,也要求頁面dom元素的顆粒度較細,否則會出現(xiàn)一個頁面有大塊空白,完全無法模擬出word生成pdf的那種效果,所以就不展開討論了。如若有讀者有比較好的解放方案,歡迎不吝賜教,感謝~??

          點個『在看』支持下?

          瀏覽 48
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黄色国产在线 | 成人AV影音 | 乱伦小视频91 | 亚洲精品乱码久久久久久蜜桃不卡 | 超碰日韩在线 |