最近遇到一个需求,需要把内容下载生成pdf文件,我采用的是jspdf+html2canvas,但转换过程中内容总是会被截断,就很难受...
技术栈:vue2+jspdf+html2canvas
一,解决思路:
判断给被截断的地方添加空白的div元素
1.拿到所有需要预防被截断的元素
2.根据页面高度进行判断
3.创建div插入进去
从页面布局考虑分为俩中情况,1.页面布局固定,2.页面布局不固定
1.页面布局固定
如果生成的网页按照与pdf固定的比例刚好是不会被截断的效果就直接解决问题了。因此只需要网页按每页pdf的宽高映射一个固定的宽高,然后按照这个固定的宽高放置不超过该大小的dom,生成pdf的代码只需要进行正常的多页pdf生成即可
2.页面布局不固定
如果网页无法进行固定大小的布局,在生成pdf的时候则需要计算每页pdf放置的dom达到刚好不被截断的边界情况。考虑到dom可能嵌套层级较多,并且对一些属性节点、文本节点不好计算高度,可以给dom元素添加标识来表示是否需要计算高度
二,代码实现
1.下载
npm install html2canvas --save
npm install jspdf --save
2.引入
import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';
3.1页面布局固定逻辑代码
printPdf() {
/*pdf出现分页时图片文宁被截断问题解决: 获取所有的需要下载的外层盒子,
循环处理这些盒子,获取当前盒子距离顶部的高度offsetTop加上盒子的高度是否大于a4纸的高度,
如果大于就在之前插入空白盒子,把内容挤下去。*/
const A4WIDTH = 592.28;
const A4HEIGHT = 841.89;
let target = document.getelementByid(pdfDom);
let pageHeight = target.scrollwidth / A4WIDTH * A4HEIGHT;
// 获取分割dom,此处为class类名为item的dom
let lableListID = target.getelementsByclassName('item')
// 进行分制操作,当dom内容已超出a4的高度,则将该dom前插入一个空dom,把他挤下去,分割
for (let i = o; i < lableListID.length; i++) {
let multiple = Math.ceil((lablelistID[i].offsetTop + lablelistID[i].offsetHeight) / pageHeight);
if (isSplit(lableListID, i, multiple * pageHeight)) {
let divParent = lableListID[i].parentNode; // 获取该div的父节点
let newNode = document.createElement(' div');
newNode.className = "emptyDiv";
newNode.style.background = "#01195e";
let _H = multiple * pageHeight - (lablelistID[i].offsetTop + lablelistID[i].offsetHeight);
newNode.style.height = _H + 30 + 'px';
newNode.style.width = '100%';
let next = lableListID[i].nextsibling; // 获取div的下一个兄弟节点
// 判断兄弟节点是否存在
console.log(next);
if (next) {
// 存在则将新节点插入到div的下一个兄弟节点之前,即div之后
divparent.insertBefore(newNode, next);
} else {
// 不存在则直接添加到最后,appendchild默认添加到divParent的最后
divparent.appendChild(newNode);
}
}
}
/* 执行下载并打印PDF的方法 */
this.pdfs(pdfDom, title);
},
// 判断是否需要添加空白div
isSplit(nodes, index, pageHeight) {
// 计算当前这块dom是否跨越了a4大小,以此分制
if (nodes[index].offsetTop + nodes[index].offsetHeight < pageleight && nodes[index + 1] && nodes[index + 1].offsetTop + nodes[index + 1].offsetHeight > pageHeight) {
return true;
}
return false;
},
//下载并打印PDF
pdfs() {
//div内部滚动导致内容不全处理
document.getElementById('app').style.height = 'auto';
setTimeout(() => {
html2canvas(document.getElementById('reportBox'), {
background: '#01195e',
allowTaint: true,
useCORS: true,
// height: document.getElementById('upload').scrollHeight,
// windowHeight: document.getElementById('upload').scrollHeight
}).then((canvas) => {
// console.log(canvas);
// var contentWidth = canvas.width;
// var contentHeight = canvas.height;
var contentWidth = parseInt(canvas.style.width) * 2;
var contentHeight = parseInt(canvas.style.height) * 2;
// console.log('contentWidth', contentWidth);
// console.log('contentHeight', contentHeight);
//一页pdf显示html页面生成的canvas高度;
var pageHeight = (contentWidth / 592.28) * 841.89;
//未生成pdf的html页面高度
var leftHeight = contentHeight;
//页面偏移
var position = 0;
//a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
var imgWidth = 595.28;
var imgHeight = (592.28 / contentWidth) * contentHeight;
// console.log('imgHeight', imgHeight);
var pageData = canvas.toDataURL('image/jpeg', 1.0);
var pdf = new jsPDF('', 'pt', 'a4');
//有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
//当内容未超过pdf一页显示的范围,无需分页
if (leftHeight < pageHeight) {
pdf.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight);
} else {
while (leftHeight > 0) {
pdf.addImage(
pageData,
'JPEG',
0,
position,
imgWidth,
imgHeight
);
leftHeight -= pageHeight;
position -= 841.89;
//避免添加空白页
if (leftHeight > 100) {
pdf.addPage();
}
}
}
});
}, 300);
},
3.2页面布局不固定逻辑代码
通过页面在浏览器的位置,计算出每一页需要分页的位置,对获取的DOM元素进行循环判断添加空白div
getAllNodes() {
// 需判断分页的节点
const lableListID = document.querySelectorAll('.ifPagingNode');
// 获取到第一页的位置信息
const pageHeight = this.$refs.contenxtPage.getBoundingClientRect();
// 处理每一页分割位置
let pageInfo = [];
for (let i = 0; i < this.pageSum + 1; i++) {
pageInfo.push(Math.ceil(pageHeight.bottom + this.height * i));
}
pageInfo.forEach((item) => {
for (let i = 0; i < lableListID.length; i++) {
// 每个元素的bottom位置
const positionInfo = lableListID[i].getBoundingClientRect();
const elementBottom = Math.ceil(positionInfo.bottom);
if (elementBottom > item - 24) {
// 新div的高度
const unllHei =
item -
24 -
18 -
lableListID[i - 1].getBoundingClientRect().bottom;
this.createEmptyElement(
lableListID[i],
unllHei,
pageHeight,
lableListID[lableListID.length - 1]
);
break;
}
}
});
},
//创建空div
createEmptyElement(currentNode, unllHei, pageHeight, endeNode) {
// 获取该div的父节点,创建新节点,设置样式
let divParent = currentNode.parentNode;
let newNode = document.createElement('div');
newNode.className = 'emptyDiv';
newNode.style.height = unllHei + 'px';
newNode.style.width = '100%';
newNode.style.marginBottom = 24 + 24 + 18 + 'px';
newNode.style.overflow = 'hidden';
newNode.style.paddingLeft = '12px';
newNode.style.paddingRight = '12px';
newNode.style.boxSizing = 'borderBox';
// 将新节点插入到div的下一个兄弟节点之前,即div之后
divParent.insertBefore(newNode, currentNode);
// 内容总高
const content = Math.ceil(
endeNode.getBoundingClientRect().bottom - pageHeight.bottom
);
//需要生成多少空白页
this.pageSum = Math.ceil(content / this.height);
/* 执行下载并打印PDF的方法 */
this.pdfs();
},
pdfs() {
// 避免出现浏览器滚动条导致的内容不全处理
// document.getElementsByClassName("reportContentBox")[0].scrollTop = 0;
//div内部滚动导致内容不全处理
document.getElementById('app').style.height = 'auto';
setTimeout(() => {
html2canvas(document.getElementById('reportBox'), {
background: '#01195e',
allowTaint: true,
useCORS: true,
// height: document.getElementById('upload').scrollHeight,
// windowHeight: document.getElementById('upload').scrollHeight
}).then((canvas) => {
// console.log(canvas);
// var contentWidth = canvas.width;
// var contentHeight = canvas.height;
var contentWidth = parseInt(canvas.style.width) * 2;
var contentHeight = parseInt(canvas.style.height) * 2;
// console.log('contentWidth', contentWidth);
// console.log('contentHeight', contentHeight);
//一页pdf显示html页面生成的canvas高度;
var pageHeight = (contentWidth / 592.28) * 841.89;
//未生成pdf的html页面高度
var leftHeight = contentHeight;
//页面偏移
var position = 0;
//a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
var imgWidth = 595.28;
var imgHeight = (592.28 / contentWidth) * contentHeight;
// console.log('imgHeight', imgHeight);
var pageData = canvas.toDataURL('image/jpeg', 1.0);
var pdf = new jsPDF('', 'pt', 'a4');
//有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
//当内容未超过pdf一页显示的范围,无需分页
if (leftHeight < pageHeight) {
pdf.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight);
} else {
while (leftHeight > 0) {
pdf.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight);
leftHeight -= pageHeight;
position -= 841.89;
//避免添加空白页
if (leftHeight > 100) {
pdf.addPage();
}
}
}
});
}, 300);
},
三,效果
注:因为布局不固定,元素的高各不相同,添加空div会造成大块的空白,需要进一步处理,如果对这块比较在意,可以下载我的代码文
github文件地址 tutoxs/myProject: chuanfangzijidexianxixiangmu (github.com)