html2canvas+jspdf實(shí)現(xiàn)下載pdf文件并添加水印
背景
一開(kāi)始實(shí)現(xiàn)長(zhǎng)截圖使用 html2canvas 直接搞定, 后來(lái)改成需要下載 pdf 文件, ... 查找相關(guān)內(nèi)容通過(guò)改造最終全部完成實(shí)現(xiàn)
- 前端實(shí)現(xiàn)pdf下
- echarts不能截?cái)?/li>
- 下載后的內(nèi)容添加水印
html部分
// 使用class 或者 ref 獲取都可以 <div class="pdfDom" ref="pdfDom"> <div data-content-group="content-group" > 不能被截?cái)嗟膬?nèi)容 </div> <div data-content-item="content-item"> 不能被截?cái)嗟膬?nèi)容 </div> <div data-content-item="content-item"> 不能被截?cái)嗟膬?nèi)容 </div> <div data-content-item="content-item"> 不能被截?cái)嗟膬?nèi)容 </div> <div data-content-group="content-group"> <div data-content-item="content-item"> 嵌套的內(nèi)容不能被截?cái)?</div> <div data-content-item="content-item"> 嵌套的內(nèi)容不能被截?cái)?</div> <div data-content-item="content-item"> 嵌套的內(nèi)容不能被截?cái)?</div> </div> </div>
js部分
import JsPdf from "jspdf"; import html2Canvas from "html2canvas"; import "./simhei-normal.js" // 解決pdf添加水印中文字體亂碼問(wèn)題 ttf轉(zhuǎn)為js后引用 // 這里使用pdf添加水印 function addWatermark (pdf, watermarkText) { // 獲取PDF頁(yè)數(shù),給PDF每一頁(yè)添加水印 var totalPages = pdf.internal.getNumberOfPages(); for (let i = 1; i <= totalPages; i++) { pdf.setPage(i); addWatermarkFill(pdf, watermarkText); } return pdf; } function addWatermarkFill (pdf, watermarkText) { let pdfW = pdf.internal.pageSize.getWidth(); let pdfH = pdf.internal.pageSize.getHeight(); // 250是可以根據(jù)水印的大小調(diào)整的 可以優(yōu)化為單水印的長(zhǎng)和寬 let xCount = pdfW / 250; let yCount = pdfH / 250; // 下面的for循環(huán)的作用是在頁(yè)面上鋪滿水印 for (let y = 0; y < yCount; y++) { let yLocation = y * 250; for (let x = 0; x < xCount; x++) { let xLocation = x * 250; pdf.saveGraphicsState() // 保存圖形狀態(tài) pdf.setFontSize(18); // 設(shè)置水印字體大小 pdf.setTextColor(200); // 設(shè)置水印顏色 // https://artskydj.github.io/jsPDF/docs/jsPDF.html#setTextColor pdf.setGState(pdf.GState({ opacity: 0.3 })) // 設(shè)置透明度為0.3 // 這里的SIMHEI是字體包 解決jsPDF 無(wú)法使用中文問(wèn)題 // pdf.setFont('Alternate-normal'); pdf.setFont('SIMHEI'); // https://artskydj.github.io/jsPDF/docs/jsPDF.html#setFont pdf.text(watermarkText, xLocation, yLocation, { align: 'left', angle: -45}); pdf.restoreGraphicsState() } } return pdf; } // 以上代碼為添加水印邏輯處理 // pdfDom 頁(yè)面dom , intervalHeight 留白間距 fileName 文件名 watermarkText: 水印內(nèi)容 export function html2Pdf (pdfDom, intervalHeight, fileName, watermarkText) { // 獲取元素的高度 function getElementHeight (element) { return element.offsetHeight; } // A4 紙寬高 const A4_WIDTH = 592.28; const A4_HEIGHT = 841.89; // 獲取元素去除滾動(dòng)條的高度 const domScrollHeight = pdfDom.scrollHeight; const domScrollWidth = pdfDom.scrollWidth; // 保存當(dāng)前頁(yè)的已使用高度 let currentPageHeight = 0; // 獲取所有的元素 我這兒是手動(dòng)給頁(yè)面添加class 用于計(jì)算高度 你也可以動(dòng)態(tài)添加 這個(gè)不重要,主要是看邏輯 // let elements = pdfDom.querySelectorAll('.content-item'); // 沒(méi)有嵌套邏輯可以直接獲取 // 代表不可被分頁(yè)的 const newPage = "content-item"; const wholeNodes = []; // 嵌套元素遍歷出來(lái) push // traversingNodes 當(dāng)不可截?cái)嘣剡^(guò)大時(shí)候 遞歸獲取子元素 function traversingNodes (nodes) { if (nodes.length === 0) return; nodes.forEach(element => { if (element.nodeType !== 1) return; // contentItem 不能截?cái)鄻?biāo)識(shí) // contentGroup 嵌套的父級(jí)標(biāo)識(shí) const { contentItem: item, contentGroup: group } = element.dataset; if (item) { const elementHeight = getElementHeight(element); console.log(elementHeight, "我是頁(yè)面上的elementHeight"); // 檢查 // 檢查添加這個(gè)元素后的總高度是否超過(guò) A4 紙的高度 if (currentPageHeight + elementHeight > A4_HEIGHT) { // 如果超過(guò)了,創(chuàng)建一個(gè)新的頁(yè)面,并將這個(gè)元素添加到新的頁(yè)面上 currentPageHeight = elementHeight; element.classList.add(newPage); console.log(element, "我是相加高度大于A4紙的元素"); } currentPageHeight += elementHeight; wholeNodes.push(element); // 不可截?cái)嘣卮嬖谇短? } else if (group) { traversingNodes(element.childNodes); } }); } traversingNodes(pdfDom.childNodes); // 遍歷所有內(nèi)容的高度 // 根據(jù) A4 的寬高等比計(jì)算 dom 頁(yè)面對(duì)應(yīng)的高度 const pageWidth = pdfDom.offsetWidth; const pageHeight = (pageWidth / A4_WIDTH) * A4_HEIGHT; // 將所有不允許被截?cái)嗟淖釉剡M(jìn)行處理 如果沒(méi)有嵌套邏輯可以直接獲取 有嵌套邏輯 通過(guò) traversingNodes 遍歷push // const wholeNodes = pdfDom.querySelectorAll(`.${newPage}`); console.log(wholeNodes, "將所有不允許被截?cái)嗟淖釉剡M(jìn)行處理"); // 插入空白塊的總高度 let allEmptyNodeHeight = 0; for (let i = 0; i < wholeNodes.length; i++) { // 判斷當(dāng)前的不可分頁(yè)元素是否在兩頁(yè)顯示 const topPageNum = Math.ceil(wholeNodes[i].offsetTop / pageHeight); const bottomPageNum = Math.ceil((wholeNodes[i].offsetTop + wholeNodes[i].offsetHeight) / pageHeight); // 是否被截?cái)? if (topPageNum !== bottomPageNum) { // 創(chuàng)建間距 const newBlock = document.createElement("div"); newBlock.className = "empty-node"; newBlock.style.background = "#FFFFFF"; // 計(jì)算空白塊的高度,可以適當(dāng)留出空間,根據(jù)自己需求而定 const _H = topPageNum * pageHeight - wholeNodes[i].offsetTop; newBlock.style.height = _H + intervalHeight + "px"; // 插入空白塊 wholeNodes[i].parentNode.insertBefore(newBlock, wholeNodes[i]); // 更新插入空白塊的總高度 allEmptyNodeHeight = allEmptyNodeHeight + _H + intervalHeight; } } // 這里可以不加 頁(yè)面高度會(huì)變 // pdfDom.setAttribute( // 'style', // `height: ${domScrollHeight + allEmptyNodeHeight}px; width: ${domScrollWidth}px;`, // ) return html2Canvas(pdfDom, { width: pdfDom.offsetWidth, height: pdfDom.offsetHeight, useCORS: true, allowTaint: true // scale: 1 }).then(canvas => { // dom 已經(jīng)轉(zhuǎn)換為 canvas 對(duì)象,可以將插入的空白塊刪除了 const emptyNodes = pdfDom.querySelectorAll(".empty-node"); for (let i = 0; i < emptyNodes.length; i++) { emptyNodes[i].style.height = 0; emptyNodes[i].parentNode.removeChild(emptyNodes[i]); } const canvasWidth = canvas.width; const canvasHeight = canvas.height; // html 頁(yè)面實(shí)際高度 let htmlHeight = canvasHeight; // 頁(yè)面偏移量 let position = 0; // 根據(jù) A4 的寬高等比計(jì)算 pdf 頁(yè)面對(duì)應(yīng)的高度 const pageHeight = (canvasWidth / A4_WIDTH) * A4_HEIGHT; // html 頁(yè)面生成的 canvas 在 pdf 中圖片的寬高 const imgWidth = A4_WIDTH; const imgHeight = 592.28 / canvasWidth * canvasHeight; // 將圖片轉(zhuǎn)為 base64 格式 const imageData = canvas.toDataURL("image/jpeg", 1.0); // 生成 pdf 實(shí)例 let PDF = new JsPdf("", "pt", "a4", true); // html 頁(yè)面的實(shí)際高度小于生成 pdf 的頁(yè)面高度時(shí),即內(nèi)容未超過(guò) pdf 一頁(yè)顯示的范圍,無(wú)需分頁(yè) if (htmlHeight <= pageHeight) { PDF.addImage(imageData, "JPEG", 0, 0, imgWidth, imgHeight); } else { while (htmlHeight > 0) { PDF.addImage(imageData, "JPEG", 0, position, imgWidth, imgHeight); // 更新高度與偏移量 htmlHeight -= pageHeight; position -= A4_HEIGHT; if (htmlHeight > 0) { // 在 PDF 文檔中添加新頁(yè)面 PDF.addPage(); } } } PDF = addWatermark(PDF, watermarkText ); // 保存 pdf 文件 PDF.save(`${fileName}.pdf`); }).catch(err => { console.log(err); }); }
調(diào)用
import { html2Pdf } from "./outpdf.js"; const pdfDom = ref(); html2Pdf(pdfDom.value, 20, "下載PDF文件", "水印文字");
水印添加問(wèn)題
添加水印的時(shí)候遇到了中文亂碼問(wèn)題 查看文檔jspdf 是支持自定義字體的
在電腦中找到找到font文件 也可以在網(wǎng)上下載對(duì)應(yīng)的 ttf 文件 我這里用的 黑體常規(guī)
ttf文件轉(zhuǎn)換為 js 文件 ttf文件轉(zhuǎn)js地址jsPdf文檔首頁(yè)中有講到Use of UTF-8 / TTF:
將轉(zhuǎn)換后的js文件,導(dǎo)入到工程內(nèi)用,在文件內(nèi)引入,用jsPDF里的 setFont()方法加載一下字體包
到此這篇關(guān)于html2canvas+jspdf實(shí)現(xiàn)下載pdf文件并添加水印的文章就介紹到這了,更多相關(guān)html2canvas jspdf下載pdf內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JS實(shí)現(xiàn)容器模塊左右拖動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了JS實(shí)現(xiàn)容器模塊左右拖動(dòng)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-01-01Moment.js實(shí)現(xiàn)多個(gè)同時(shí)倒計(jì)時(shí)
這篇文章主要為大家詳細(xì)介紹了Moment.js實(shí)現(xiàn)多個(gè)同時(shí)倒計(jì)時(shí),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08bootstrap confirmation按鈕提示組件使用詳解
這篇文章主要為大家詳細(xì)介紹了bootstrap confirmation按鈕提示組件的使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08JavaScript常用語(yǔ)句循環(huán),判斷,字符串換數(shù)字
這篇文章主要介紹了JavaScript常用語(yǔ)句主要包括對(duì)循環(huán),判斷,字符串換數(shù)字相關(guān)資料的介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下具體內(nèi)容2021-12-12JavaScript定時(shí)器設(shè)置、使用與倒計(jì)時(shí)案例詳解
這篇文章主要介紹了JavaScript定時(shí)器設(shè)置、使用與倒計(jì)時(shí)案例,詳細(xì)分析了javascript定時(shí)器的設(shè)置、取消、循環(huán)調(diào)用并附帶一個(gè)倒計(jì)時(shí)功能應(yīng)用案例,需要的朋友可以參考下2019-07-07SlideView 圖片滑動(dòng)(擴(kuò)展/收縮)展示效果
滑動(dòng)展示效果主要用在圖片或信息的滑動(dòng)展示,也可以設(shè)置一下做成簡(jiǎn)單的口風(fēng)琴(Accordion)效果。2010-08-08百度判斷手機(jī)終端并自動(dòng)跳轉(zhuǎn)js代碼及使用實(shí)例
這篇文章主要介紹了百度判斷手機(jī)終端并自動(dòng)跳轉(zhuǎn)js代碼及使用實(shí)例,需要的朋友可以參考下2014-06-06JS實(shí)現(xiàn)隨機(jī)顏色的3種方法與顏色格式的轉(zhuǎn)化
隨機(jī)顏色和顏色格式是我們?cè)陂_(kāi)發(fā)中經(jīng)常要用到的一個(gè)小功能,網(wǎng)上相關(guān)的資料也很多,想著有必要總結(jié)一下自己的經(jīng)驗(yàn)。所以這篇文章主要介紹了JS實(shí)現(xiàn)隨機(jī)顏色的3種方法與顏色格式的轉(zhuǎn)化,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-01-01