佳木斯湛栽影视文化发展公司

主頁 > 知識庫 > Html5基于canvas實(shí)現(xiàn)電子簽名并生成PDF文檔

Html5基于canvas實(shí)現(xiàn)電子簽名并生成PDF文檔

熱門標(biāo)簽:使用U盤裝系統(tǒng) 檢查注冊表項(xiàng) 阿里云 智能手機(jī) 百度競價(jià)點(diǎn)擊價(jià)格的計(jì)算公式 網(wǎng)站建設(shè) 硅谷的囚徒呼叫中心 美圖手機(jī)

前言

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

實(shí)現(xiàn)思路

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

生成簽名

1. 在tsx中定義canvas畫布

<canvas className={styles.canvas} ref={canvasDom} width="350" height="150" />

注意:Canvas的寬高必須要使用內(nèi)聯(lián)樣式定義,這是因?yàn)镃anvas標(biāo)簽有自己的默認(rèn)寬高300px×150px。它內(nèi)聯(lián)樣式定義的width和height是繪畫區(qū)域(畫布)實(shí)際寬度和高度,繪制的圖形都是在這個(gè)上面。如果在style外鏈文件中定義其width和height,那么這個(gè)width和height是Canvas在瀏覽器中被渲染的高度和寬度。如果Canvas中沒有直接定義width和height沒或值不正確,就會被設(shè)置成默認(rèn)值{width:300px,height:150px}。所以,如果你在style中外鏈文件中設(shè)置了canvas {width: 200px; height: 200px;},卻沒有直接在canvas上定義畫布寬高,那么此時(shí)你輸出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)這個(gè)坐標(biāo)點(diǎn)開始畫圖
    ctx.lineTo(stopX, stopY);  // 定義從(beginX, beginY)到(stopX, stopY)的線條(該方法不會創(chuàng)建線條)
    ctx.closePath();  // 創(chuàng)建該條路徑
    ctx.stroke();  // 實(shí)際地繪制出通過 moveTo() 和 lineTo() 方法定義的路徑。默認(rèn)顏色是黑色。
  };

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畫布上簽名的時(shí)候頁面跟著滾動
      beginX = event.touches[0].clientX - this.offsetLeft; 
      beginY = event.touches[0].pageY - this.offsetTop;
    });
    canvas.addEventListener('touchmove', (event: any) => {
      event.preventDefault(); // 阻止在canvas畫布上簽名的時(shí)候頁面跟著滾動
      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)鍵,需要不斷更新起點(diǎn),否則畫出來的是射線簇
      beginY = stopY;
    });

注意

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

需要理清移動端事件對象的幾個(gè)屬性,⏬

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

在簽名(touchmove)這個(gè)動作過程中,我們需要不斷的更新起點(diǎn)位置,否則畫出來是這樣🔽


 

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

生成PDF文檔

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

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

注意:此處需要設(shè)置width和height及x,y,否則當(dāng)頁面內(nèi)容只有一頁的時(shí)候沒有問題,但是若頁面內(nèi)容有很多頁的時(shí)候,就會出現(xiàn)生成的圖片只有一小部分有內(nèi)容的現(xiàn)象。問題就出現(xiàn)在這個(gè)配置參數(shù)上,若沒有設(shè)置寬高,則默認(rèn)只取當(dāng)前視口的內(nèi)容,丟棄掉其他超出當(dāng)前視口的內(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 < pageHeight) { //
        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文檔,但是當(dāng)PDF頁數(shù)有很多的時(shí)候,會有這樣的問題⏬

 

可以看到,分頁的時(shí)候從這段文字這里懶腰截?cái)嗔恕_@顯然不是我們想要看到的效果,如何解決這個(gè)問題呢?🤔

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

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

PDF文檔頁數(shù)較多

對于這種情況,筆者嘗試遍歷要打印的dom節(jié)點(diǎn)的子節(jié)點(diǎn),將每一頁所能打印的dom節(jié)點(diǎn)高度累加,若超過了頁面所能承載的最大高度,則將最后一個(gè)節(jié)點(diǎn)增加padding,打印完畢將樣式還原。這種方法因?yàn)橐?jì)算每個(gè)dom節(jié)點(diǎn)的高度,非常耗性能,也要求頁面dom元素的顆粒度較細(xì),否則會出現(xiàn)一個(gè)頁面有大塊空白,完全無法模擬出word生成pdf的那種效果,所以就不展開討論了。

到此這篇關(guān)于Html5基于canvas實(shí)現(xiàn)電子簽名并生成PDF文檔的文章就介紹到這了,更多相關(guān)canvas電子簽名內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持腳本之家!

標(biāo)簽:煙臺 湖北 山南 懷化 黃山 湘潭 賀州 通遼

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《Html5基于canvas實(shí)現(xiàn)電子簽名并生成PDF文檔》,本文關(guān)鍵詞  ;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。
  • 相關(guān)文章
  • 收縮
    • 微信客服
    • 微信二維碼
    • 電話咨詢

    • 400-1100-266
    遂平县| 万源市| 城口县| 明溪县| 铜鼓县| 双城市| 云南省| 那坡县| 平阳县| 新竹市| 梁山县| 普陀区| 千阳县| 乌拉特中旗| 油尖旺区| 固始县| 筠连县| 罗甸县| 卓资县| SHOW| 东阳市| 永嘉县| 明水县| 平湖市| 交口县| 阳原县| 同仁县| 丹巴县| 瓦房店市| 宁强县| 汉川市| 敖汉旗| 西吉县| 石屏县| 社旗县| 岳普湖县| 新郑市| 巍山| 托克托县| 金坛市| 桂东县|