html2canvas+jspdf實(shí)現(xiàn)下載pdf文件并添加水印
背景
一開始實(shí)現(xiàn)長截圖使用 html2canvas 直接搞定, 后來改成需要下載 pdf 文件, ... 查找相關(guān)內(nèi)容通過改造最終全部完成實(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添加水印中文字體亂碼問題 ttf轉(zhuǎn)為js后引用
// 這里使用pdf添加水印
function addWatermark (pdf, watermarkText) {
// 獲取PDF頁數(shù),給PDF每一頁添加水印
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)化為單水印的長和寬
let xCount = pdfW / 250;
let yCount = pdfH / 250;
// 下面的for循環(huán)的作用是在頁面上鋪滿水印
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 無法使用中文問題
// 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 頁面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)前頁的已使用高度
let currentPageHeight = 0;
// 獲取所有的元素 我這兒是手動(dòng)給頁面添加class 用于計(jì)算高度 你也可以動(dòng)態(tài)添加 這個(gè)不重要,主要是看邏輯
// let elements = pdfDom.querySelectorAll('.content-item'); // 沒有嵌套邏輯可以直接獲取
// 代表不可被分頁的
const newPage = "content-item";
const wholeNodes = []; // 嵌套元素遍歷出來 push
// traversingNodes 當(dāng)不可截?cái)嘣剡^大時(shí)候 遞歸獲取子元素
function traversingNodes (nodes) {
if (nodes.length === 0) return;
nodes.forEach(element => {
if (element.nodeType !== 1) return;
// contentItem 不能截?cái)鄻?biāo)識
// contentGroup 嵌套的父級標(biāo)識
const { contentItem: item, contentGroup: group } = element.dataset;
if (item) {
const elementHeight = getElementHeight(element);
console.log(elementHeight, "我是頁面上的elementHeight"); // 檢查
// 檢查添加這個(gè)元素后的總高度是否超過 A4 紙的高度
if (currentPageHeight + elementHeight > A4_HEIGHT) {
// 如果超過了,創(chuàng)建一個(gè)新的頁面,并將這個(gè)元素添加到新的頁面上
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īng)的高度
const pageWidth = pdfDom.offsetWidth;
const pageHeight = (pageWidth / A4_WIDTH) * A4_HEIGHT;
// 將所有不允許被截?cái)嗟淖釉剡M(jìn)行處理 如果沒有嵌套邏輯可以直接獲取 有嵌套邏輯 通過 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)前的不可分頁元素是否在兩頁顯示
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;
}
}
// 這里可以不加 頁面高度會(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 對象,可以將插入的空白塊刪除了
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 頁面實(shí)際高度
let htmlHeight = canvasHeight;
// 頁面偏移量
let position = 0;
// 根據(jù) A4 的寬高等比計(jì)算 pdf 頁面對應(yīng)的高度
const pageHeight = (canvasWidth / A4_WIDTH) * A4_HEIGHT;
// html 頁面生成的 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 頁面的實(shí)際高度小于生成 pdf 的頁面高度時(shí),即內(nèi)容未超過 pdf 一頁顯示的范圍,無需分頁
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 文檔中添加新頁面
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文件", "水印文字");
水印添加問題
添加水印的時(shí)候遇到了中文亂碼問題 查看文檔jspdf 是支持自定義字體的
在電腦中找到找到font文件 也可以在網(wǎng)上下載對應(yīng)的 ttf 文件 我這里用的 黑體常規(guī)

ttf文件轉(zhuǎn)換為 js 文件 ttf文件轉(zhuǎn)js地址jsPdf文檔首頁中有講到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)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JS實(shí)現(xiàn)容器模塊左右拖動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了JS實(shí)現(xiàn)容器模塊左右拖動(dòng)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-01-01
Moment.js實(shí)現(xiàn)多個(gè)同時(shí)倒計(jì)時(shí)
這篇文章主要為大家詳細(xì)介紹了Moment.js實(shí)現(xiàn)多個(gè)同時(shí)倒計(jì)時(shí),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08
JS實(shí)現(xiàn)點(diǎn)擊下拉列表文本框中出現(xiàn)對應(yīng)的網(wǎng)址,點(diǎn)擊跳轉(zhuǎn)按鈕實(shí)現(xiàn)跳轉(zhuǎn)
這篇文章主要介紹了JS實(shí)現(xiàn)點(diǎn)擊下拉列表文本框中出現(xiàn)對應(yīng)的網(wǎng)址,點(diǎn)擊跳轉(zhuǎn)按鈕實(shí)現(xiàn)跳轉(zhuǎn),本文給大家分享實(shí)例代碼,代碼簡單易懂,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-11-11
bootstrap confirmation按鈕提示組件使用詳解
這篇文章主要為大家詳細(xì)介紹了bootstrap confirmation按鈕提示組件的使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08
JavaScript常用語句循環(huán),判斷,字符串換數(shù)字
這篇文章主要介紹了JavaScript常用語句主要包括對循環(huán),判斷,字符串換數(shù)字相關(guān)資料的介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下具體內(nèi)容2021-12-12
JavaScript定時(shí)器設(shè)置、使用與倒計(jì)時(shí)案例詳解
這篇文章主要介紹了JavaScript定時(shí)器設(shè)置、使用與倒計(jì)時(shí)案例,詳細(xì)分析了javascript定時(shí)器的設(shè)置、取消、循環(huán)調(diào)用并附帶一個(gè)倒計(jì)時(shí)功能應(yīng)用案例,需要的朋友可以參考下2019-07-07
SlideView 圖片滑動(dòng)(擴(kuò)展/收縮)展示效果
滑動(dòng)展示效果主要用在圖片或信息的滑動(dòng)展示,也可以設(shè)置一下做成簡單的口風(fēng)琴(Accordion)效果。2010-08-08
百度判斷手機(jī)終端并自動(dòng)跳轉(zhuǎn)js代碼及使用實(shí)例
這篇文章主要介紹了百度判斷手機(jī)終端并自動(dòng)跳轉(zhuǎn)js代碼及使用實(shí)例,需要的朋友可以參考下2014-06-06
JS實(shí)現(xiàn)隨機(jī)顏色的3種方法與顏色格式的轉(zhuǎn)化
隨機(jī)顏色和顏色格式是我們在開發(fā)中經(jīng)常要用到的一個(gè)小功能,網(wǎng)上相關(guān)的資料也很多,想著有必要總結(jié)一下自己的經(jīng)驗(yàn)。所以這篇文章主要介紹了JS實(shí)現(xiàn)隨機(jī)顏色的3種方法與顏色格式的轉(zhuǎn)化,需要的朋友可以參考借鑒,下面來一起看看吧。2017-01-01

