这周公司提了一个需求,就是一键导出几个 tab 页pdf,在网上查找了不少资料,关于使用html2canvas,jsPDF导出的内容截断的帖子不少,但大多都是最基础的导出,即便有相关的内容截断的帖子,大多也不太行... 所以我就在网上找了几个代码片段杂糅在一起,然后稍微改了改
其实内容和表格的处理思路是差不多的,需要给有截断风险的dom元素添加上特定的class,计算出最后一个不超过 A4 高度的元素,其底部到 A4 底部的高度进行遮白处理,其下一个元素要增加到下一页,这就是内容截断的处理思路
downloadPdf() {
console.log("printing");
const first = document.getElementById("first");
const second = document.getElementById("second");
const three = document.getElementById("three");
const four = document.getElementById("four");
const five = document.getElementById("five");
// let list = [first, second, three];
let list = [
{
name: "first",
label:'tab1',
dom: first,
},
{
name: "four",
label:'tab2',
dom: four,
},
{
name: "second",
label:'tab3',
dom: second,
},
{
name: "three",
label:'tab4',
dom: three,
},
{
name: "five",
label:'tab5',
dom: five,
},
];
// 使用递归逐个生成 PDF
this.generatePDFRecursively(list, 0, new JSZip());
},
generatePDFRecursively(list, index, zip) {
if (index >= list.length) {
console.log("All PDFs generated");
// 生成 ZIP 文件并下载
// 生成 ZIP 文件并下载
zip.generateAsync({ type: "blob" }).then((content) =>{
saveAs(content, `${this.data.answer['姓名']}简历.zip`);
});
return;
}
const htmlContent = list[index].dom;
this.activeName = list[index].name;
this.$nextTick(async () => {
const pdfBlob = await this.generatePDF(htmlContent);
zip.file(`${this.data.answer['姓名']}${list[index].label}.pdf`, pdfBlob);
// 递归处理下一个元素
this.generatePDFRecursively(list, index + 1, zip);
});
},
async generatePDF(htmlContent) {
//A4纸张的宽度和高度
const A4_WIDTH = 592.28;
const A4_HEIGHT = 841.89;
//获取DOM节点,这个节点包含的是你想要导出的PDF内容
const printDom = htmlContent;
//计算页面的高度
const pageHeight =
(printDom.getBoundingClientRect().width / A4_WIDTH) * A4_HEIGHT;
//获取DOM节点,是可能会被截断的DOM节点
const wholeNodes = document.querySelectorAll(".whole-node");
for (let i = 0; i < wholeNodes.length; i++) {
let headNum = 0; // 当前元素的上边界所在的页码
let tailNum = 0; // 当前元素的下边界所在的页码
if (wholeNodes[i].nodeName == "TR") {
// 针对表格特殊处理
var startPos = printDom.getBoundingClientRect().top; // 获取当前pdf内容相对于当前页面窗口的偏移量
var nowPos = wholeNodes[i].getBoundingClientRect().top; // 当前元素的偏移量
// nowPos - startPos表示 当前元素的上边界 距离 pdf内容的上边界 的高度
headNum = Math.ceil((nowPos - startPos) / pageHeight);
tailNum = Math.ceil(
(nowPos - startPos + wholeNodes[i].offsetHeight) / pageHeight
);
if (headNum !== tailNum) {
// 页码不一致,说明该元素会被截断
// 2、计算插入空白块的高度
let _H = headNum * pageHeight - (nowPos - startPos);
// 3、插入空白块使被截断元素下移
let divParent = wholeNodes[i].parentNode;
let newNode = document.createElement("div");
newNode.className = "emptyDiv";
newNode.style.background = "#ffffff";
newNode.style.height = _H + 10 + "px"; // 适当留出一点距离
divParent.insertBefore(newNode, wholeNodes[i]);
}
} else {
//计算分页的页数
const bottomPageNum = Math.ceil(
(wholeNodes[i].offsetTop + wholeNodes[i].offsetHeight) / pageHeight
);
//这个条件判断是元素i距离上方或上层控件的位置+元素i本身的高度小于A4纸的高度,并且下一个元素距离上方或上层控件的位置+下一个元素本身的高度大于A4纸的高度意味着当前页面的内容则在中间插入一个空白块,空白的高度计算为:A4纸的高度减去减去元素i的offsetTop+offsetHeight需要分页处理。
if (
wholeNodes[i].offsetTop + wholeNodes[i].offsetHeight <
pageHeight * bottomPageNum &&
wholeNodes[i + 1] &&
wholeNodes[i + 1].offsetTop + wholeNodes[i + 1].offsetHeight >
pageHeight * bottomPageNum
) {
const divParent = wholeNodes[i].parentNode;
const newBlock = document.createElement("div");
newBlock.className = "emptyDiv";
newBlock.style.background = "#fff";
//计算空白区域的高度,以便正确地填充在当前页面和下一页面之间。_H 是当前节点到底部的距离,32px 是一个额外的空间,作为安全边距或分页缓冲
const _H =
bottomPageNum * pageHeight -
wholeNodes[i].offsetTop -
wholeNodes[i].offsetHeight;
newBlock.style.height = `${_H + 32}px`;
const next = wholeNodes[i].nextSibling;
if (next) {
divParent.insertBefore(newBlock, wholeNodes[i]);
} else {
divParent.appendChild(newBlock);
}
}
}
}
//使用 html2canvas 库将 printDom 元素的内容渲染成一个图像(canvas)
const canvas = await html2canvas(printDom, {
useCORS: true,
scale: 2,
});
//使用 html2canvas 再次渲染 printDom,并在渲染完成后执行 then() 回调,删除刚刚创建的空白占位元素。
html2canvas(printDom, {
height: printDom.getBoundingClientRect().height,
width: printDom.getBoundingClientRect().width,
allowTaint: true,
}).then(() => {
const emptyDivs = document.querySelectorAll(".emptyDiv");
for (let i = 0; i < emptyDivs.length; i++) {
emptyDivs[i].parentNode.removeChild(emptyDivs[i]);
}
});
const contentWidth = parseInt(canvas.style.width) * 2;
const contentHeight = parseInt(canvas.style.height) * 2;
const imgWidth = 592.28;
const imgHeight = (592.28 / contentWidth) * contentHeight;
const pageData = canvas.toDataURL("image/jpeg", 1.0);
const PDF = new jsPDF("p", "pt", "a4");
let leftHeight = contentHeight;
//初始化 leftHeight 为内容的总高度,position 用于控制每一页图像的位置。
let position = 0;
//判断 leftHeight 是否小于等于页面的高度 pageHeight。如果小于等于,直接将图像添加到 PDF 中。如果大于,则分多页添加图像,每页添加 841.89 的高度,直到所有内容都被添加到 PDF 中。position 用于控制图像在每页中的垂直位置。
if (leftHeight <= pageHeight) {
PDF.addImage(pageData, "JPEG", 0, 0, imgWidth, imgHeight);
} else {
while (leftHeight > 0) {
PDF.addImage(pageData, "JPEG", 0, position, imgWidth, imgHeight);
leftHeight -= (contentWidth / 592.28) * 841.89;
position -= 841.89;
if (leftHeight > 0) {
PDF.addPage();
}
}
}
return PDF.output('blob');
}