1、解决了dom节点过长,pdf分页不连续问题
2、解决dom节点中存在视频,音频无法导出为pdf问题(大佬们有好的建议欢迎讨论)
话不多说 直接showCode
一、安装插件
import jsPDF from 'jspdf'
import html2canvas from 'html2canvas'
二、具体实现
版本一: 包含分页功能,导出的dom节点为普通节点(不包含视频,硬盘)
// import { set } from 'core-js/core/dict';
import html2Canvas from 'html2canvas';
import { jsPDF } from 'jspdf';
// pdfDom 页面 dom, intervalHeight 留白间距, fileName 文件名
export function html2Pdf(pdfDom, intervalHeight, fileName ,callback) {
// 获取元素的高度
function getElementHeight(element) {
return element.offsetHeight;
}
// A4 纸宽高
const A4_WIDTH = 592.28, A4_HEIGHT = 841.89;
// 获取元素去除滚动条的高度
const domScrollHeight = pdfDom.scrollHeight;
const domScrollWidth = pdfDom.scrollWidth;
// 保存当前页的已使用高度
let currentPageHeight = 0;
// 获取所有的元素 我这儿是手动给页面添加 class 用于计算高度 你也可以动态添加 这个不重要,主要是看逻辑
let elements = pdfDom.querySelectorAll('.element');
// 代表不可被分页
let newPage = 'new-page';
// 遍历所有内容的高度
for (let element of elements) {
let elementHeight = getElementHeight(element);
// 检查添加这个元素后的总高度是否超过 A4 纸的高度
if (currentPageHeight + elementHeight > A4_HEIGHT) {
// 如果超过了,创建一个新的页面,并将这个元素添加到新的页面上
currentPageHeight = elementHeight;
element.classList.add(newPage);
}
currentPageHeight += elementHeight;
}
// 根据 A4 的宽高等比计算 dom 页面对应的高度
const pageWidth = pdfDom.offsetWidth;
const pageHeight = (pageWidth / A4_WIDTH) * A4_HEIGHT;
// 将所有不允许被截断的子元素进行处理
const wholeNodes = pdfDom.querySelectorAll(`.${newPage}`);
// 插入空白块的总高度
let allEmptyNodeHeight = 0;
for (let i = 0; i < wholeNodes.length; i++) {
// 判断当前的不可分页元素是否在两页显示
const topPageNum = Math.ceil(wholeNodes[i].offsetTop / pageHeight);
const bottomPageNum = Math.ceil((wholeNodes[i].offsetTop + wholeNodes[i].offsetHeight) / pageHeight);
// 是否被截断
if (topPageNum!== bottomPageNum) {
// 创建间距
const newBlock = document.createElement('div');
newBlock.className = 'empty-node';
newBlock.style.background = '#fff';
// 计算空白块的高度,可以适当留出空间,根据自己需求而定
const _H = topPageNum * pageHeight - wholeNodes[i].offsetTop;
newBlock.style.height = _H + intervalHeight + 'px';
// 插入空白块
wholeNodes[i].parentNode.insertBefore(newBlock, wholeNodes[i]);
// 更新插入空白块的总高度
allEmptyNodeHeight = allEmptyNodeHeight + _H + intervalHeight;
}
}
pdfDom.setAttribute(
'style',
`height: ${domScrollHeight + allEmptyNodeHeight}px; width: ${domScrollWidth}px;`,
);
console.log('%c [ pdfDom.offsetWidth ]-68', 'font-size:13px; background:pink; color:#bf2c9f;', pdfDom.offsetHeight,pdfDom.offsetWidth)
return html2Canvas(pdfDom, {
width: pdfDom.offsetWidth,
height: pdfDom.offsetHeight,
useCORS: true,
allowTaint: true,
scale: 3,
}).then(canvas => {
// dom 已经转换为 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, canvasHeight = canvas.height;
// html 页面实际高度
let htmlHeight = canvasHeight;
// 页面偏移量
let position = 0;
// 根据 A4 的宽高等比计算 pdf 页面对应的高度
const pageHeight = (canvasWidth / A4_WIDTH) * A4_HEIGHT;
// html 页面生成的 canvas 在 pdf 中图片的宽高
const imgWidth = A4_WIDTH;
const imgHeight = 592.28 / canvasWidth * canvasHeight;
// 将图片转为 base64 格式
const imageData = canvas.toDataURL('image/jpeg', 1.0);
// 生成 pdf 实例
const PDF = new jsPDF('', 'pt', 'a4', true);
// html 页面的实际高度小于生成 pdf 的页面高度时,即内容未超过 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 文件
PDF.save(`${fileName}.pdf`);
callback(null);
}).catch(err => {
callback(err);
});
}
版本二: dom节点中包含视频,音频 需要转化成网址在展示
// import { set } from 'core-js/core/dict';
import html2Canvas from 'html2canvas';
import { jsPDF } from 'jspdf';
// pdfDom 页面 dom, intervalHeight 留白间距, fileName 文件名
export function html2Pdf(pdfDom, intervalHeight, fileName ,callback) {
const hasVideo = hasVideoTagInElement(pdfDom);
console.log('%c [ hasVideo ]-8', 'font-size:13px; background:pink; color:#bf2c9f;', hasVideo)
// 获取元素的高度
function getElementHeight(element) {
return element.offsetHeight;
}
// A4 纸宽高
const A4_WIDTH = 592.28, A4_HEIGHT = 841.89;
// 获取元素去除滚动条的高度
const domScrollHeight = pdfDom.scrollHeight;
const domScrollWidth = pdfDom.scrollWidth;
// 保存当前页的已使用高度
let currentPageHeight = 0;
// 获取所有的元素 我这儿是手动给页面添加 class 用于计算高度 你也可以动态添加 这个不重要,主要是看逻辑
let elements = pdfDom.querySelectorAll('.element');
// 代表不可被分页
let newPage = 'new-page';
// 遍历所有内容的高度
for (let element of elements) {
let elementHeight = getElementHeight(element);
// 检查添加这个元素后的总高度是否超过 A4 纸的高度
if (currentPageHeight + elementHeight > A4_HEIGHT) {
// 如果超过了,创建一个新的页面,并将这个元素添加到新的页面上
currentPageHeight = elementHeight;
element.classList.add(newPage);
}
currentPageHeight += elementHeight;
}
// 根据 A4 的宽高等比计算 dom 页面对应的高度
const pageWidth = pdfDom.offsetWidth;
const pageHeight = (pageWidth / A4_WIDTH) * A4_HEIGHT;
// 将所有不允许被截断的子元素进行处理
const wholeNodes = pdfDom.querySelectorAll(`.${newPage}`);
// 插入空白块的总高度
let allEmptyNodeHeight = 0;
for (let i = 0; i < wholeNodes.length; i++) {
// 判断当前的不可分页元素是否在两页显示
const topPageNum = Math.ceil(wholeNodes[i].offsetTop / pageHeight);
const bottomPageNum = Math.ceil((wholeNodes[i].offsetTop + wholeNodes[i].offsetHeight) / pageHeight);
// 是否被截断
if (topPageNum!== bottomPageNum) {
// 创建间距
const newBlock = document.createElement('div');
newBlock.className = 'empty-node';
newBlock.style.background = '#fff';
// 计算空白块的高度,可以适当留出空间,根据自己需求而定
const _H = topPageNum * pageHeight - wholeNodes[i].offsetTop;
newBlock.style.height = _H + intervalHeight + 'px';
// 插入空白块
wholeNodes[i].parentNode.insertBefore(newBlock, wholeNodes[i]);
// 更新插入空白块的总高度
allEmptyNodeHeight = allEmptyNodeHeight + _H + intervalHeight;
}
}
pdfDom.setAttribute(
'style',
`height: ${domScrollHeight + allEmptyNodeHeight}px; width: ${domScrollWidth}px;`,
);
return html2Canvas(pdfDom, {
width: pdfDom.offsetWidth,
height: pdfDom.offsetHeight,
useCORS: true,
allowTaint: true,
scale: 3,
}).then(canvas => {
// dom 已经转换为 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, canvasHeight = canvas.height;
// html 页面实际高度
let htmlHeight = canvasHeight;
// 页面偏移量
let position = 0;
// 根据 A4 的宽高等比计算 pdf 页面对应的高度
const pageHeight = (canvasWidth / A4_WIDTH) * A4_HEIGHT;
// html 页面生成的 canvas 在 pdf 中图片的宽高
const imgWidth = A4_WIDTH;
const imgHeight = 592.28 / canvasWidth * canvasHeight;
// 将图片转为 base64 格式
const imageData = canvas.toDataURL('image/jpeg', 1.0);
// 生成 pdf 实例
const PDF = new jsPDF('', 'pt', 'a4', true);
// html 页面的实际高度小于生成 pdf 的页面高度时,即内容未超过 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 文件
PDF.save(`${fileName}.pdf`);
callback(null);
}).catch(err => {
callback(err);
});
}
function hasVideoTagInElement(element) {
if (element.tagName && element.tagName.toLowerCase() === 'video') {
const newP = document.createElement('div');
newP.textContent = element.src;
element.parentNode.replaceChild(newP, element);
return true;
}
if (element.tagName && element.tagName.toLowerCase() === 'audio') {
const newP = document.createElement('div1');
newP.textContent = element.src;
element.parentNode.replaceChild(newP, element);
}
let children = element.childNodes;
for (let i = 0; i < children.length; i++) {
let child = children[i];
if (child.nodeType === 1 && hasVideoTagInElement(child)) {
return true;
}
}
return false;
}
具体使用:
import { html2Pdf } from '@/utils/htmlToPdf'
注意 使用refs引用 或者id
exportReportFile() {
html2Pdf(this.$refs.editorHtml, 20, `任务:${this.baseData.taskName}检测报告`, (err) => {
if (err) {
this.$message.error("生成 PDF 失败!");
} else {
this.$message.success("生成 PDF 成功!");
}
if (this.baseData.modalType == 'video' || this.baseData.modalType == 'audio') {
location.reload();
}
})
},
分页数据高度可根据业务调整,分辨率问题 调整: scale参数到合适的为止
因篇幅问题不能全部显示,请点此查看更多更全内容