一、前言
个人网站上线了,欢迎大家访问 苏浩的个人博客
使用的第三方:html2canvas 和 jspdf
为了一劳永逸(更好的偷懒),做了一个简历修改的页面,将简历信息保存到数据库同时使用html2canvas 和 jspdf 导出PDF,但是在导出PDF时却发现文本内容在分页部分被直接截断,经过查阅资料没找到匹配的结果,于是就自己想办法解决吧。
二、正文
首先是导出PDF的工具方法,直接修改Vue的原型方便调用
Vue.prototype.getPdf = function(id, title) {
html2Canvas(document.querySelector(`#${id}`), {
useCORS: true
}).then(function(canvas) {
let contentWidth = canvas.width
let contentHeight = canvas.height
let pageHeight = contentWidth / 592.28 * 841.89
let leftHeight = contentHeight
let position = 0
let imgWidth = 595.28
let imgHeight = 592.28 / contentWidth * contentHeight
let pageData = canvas.toDataURL('image/jpeg', 1.0)
let PDF = new JsPDF('', 'pt', 'a4')
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 > 0) {
PDF.addPage()
}
}
}
PDF.save(title + '.pdf')
})
}
需要打印的结构是这样的,多个子组件
<div id="preview">
<resume-header id="resume-header"></resume-header>
<basic-info :userInfo="resume.userInfo" class="p40" id="resume-info" style="padding-top:40px;"></basic-info>
<education class="p40" id="education"></education>
<project :project="resume.project" class="p40" id="project"></project>
<practice :practice="resume.practice" class="p40" id="practice"></practice>
<work :work="resume.work" class="p40" id="work"></work>
<skill :skill="resume.skill" class="p40" id="skill"></skill>
<self-introduction :selfIntroduction="resume.selfIntroduction" class="p40" id="self"
style="padding-bottom:40px;"></self-introduction>
</div>
1. 最初思路
思路:定义一个DOM容器,遍历所有需要打印的节点,并逐个添加到这个DOM容器内,每次添加之前先判断这个添加进去后是否会超出一页的高度,如果没超出,则添加进去,否则证明快到一页的结尾了,则当前页剩余部分填充一个空白div。
这个思路比较简单,贴一段伪代码吧
function printPDF() {
let children = document.getElementById("你需要打印的dom节点").children;
let box = document.createElement("div");
const pageHeight = 1500;
let height = 0;
for(let i=0; i<children.length; i++) {
let node = children[i];
if(node.offsetHeight + height < pageHeight) {
height += node.offsetHeight;
box.appendChild(node.cloneNode(true));
} else {
let empty = document.createElement("div");
empty.style.height = pageHeight - height;
box.appendChild(empty);
height = 0;
}
}
}
2. 改进思路
经过尝试,确实会准确分页,也不会裁断,但是发现个问题,如果有个dom节点很高,几乎占据了一页,哪怕第一页只有少量内容,也会填充一大片空白,看起来很不友好。
原因很简单,因为在循环时,这个节点加进去后会超出一页高度,所以剩余部分默认填充了空白div,也就是上面代码循环中else部分。
改进方法,在else分支中判断剩余节点,看看剩余节点中是否有节点可以放置到当前页中,如果有,则添加到当前页中,如果没有,只能填充空白了(或者将子节点拆分成多个更小的节点也可以)
思路:
第一步:维护一个数组printOrderArr,保存需要往dom容器中追加节点的顺序(包括填充),遍历思路和上面一样,不过改进了超出当前页的判断,如果超出当前页,先在剩余节点中查找是否有其余节点可以添加进来,如果实在没有在填充空白。
第二步:遍历printOrderArr,如果当前遍历对象不是空白填充,则在子节点children中查找对应节点添加到容器中,否则填充空白。
第三步:打印容器中的内容
function exportPDF() {
let dom = document.getElementById("preview");
let children = dom.children[0].children;
let data = Array.prototype.map.call(children, item => {
return {
id: item.id,
height: item.offsetHeight
}
})
let domHeight = 0;
let printOrderArr = [];
for (let i = 0; i < 3; i++) {
printOrderArr.push({
id: data[i].id,
isEmpty: false
})
domHeight += data[i].height
}
data.splice(0, 3);
while (data.length) {
const nodeHeight = data[0].height;
if ((domHeight + nodeHeight) < this.pageHeight) {
printOrderArr.push({
id: data[0].id,
isEmpty: false
})
domHeight += nodeHeight;
data.splice(0, 1);
} else {
const lastHeight = this.pageHeight - domHeight;
let node;
for (let j = 0; j < data.length; j++) {
if (data[j].height < lastHeight) {
node = data[j];
data.splice(j, 1);
}
}
if (node) {
printOrderArr.push({
id: node.id,
isEmpty: false
});
domHeight += node.height;
} else {
printOrderArr.push({
height: lastHeight,
isEmpty: true
})
domHeight = 0;
}
}
}
let pdfDom = document.createElement("div");
pdfDom.id = "pdf";
for (let i = 0; i < printOrderArr.length; i++) {
let node = printOrderArr[i];
if (!node.isEmpty) {
let dom = Array.prototype.filter.call(children, item => item.id == node.id)[0];
pdfDom.appendChild(dom.cloneNode(true));
} else {
let empty = document.createElement("div");
empty.className = "empty";
empty.style.height = node.height + 'px';
pdfDom.appendChild(empty);
}
}
let container = document.getElementsByClassName("resume-container")[0];
container.appendChild(pdfDom);
this.getPdf("pdf", "测试打印");
container.removeChild(pdfDom)
},
经过改进后,打印出来的效果好多了,至少不会出现大片的空白了,因为我的节点都是整体存在,才会出现这个情况,如果子节点不是整体,则第一种方法也可以
三、总结
做完这个功能突然意识到这个解决思路好像和leetcode中的一道题很像,逐渐明白算法并不是离我们很远。
程序 = 算法+ 数据结构绝不是书本空谈,这个需求中printOrderArr就是数据结构的体现,思路就是算法,合在一起就是一个程序。前辈们将业务抽离出去,只保留算法,就是为了让我们学明白算法再来更好的写程序,然而往往很多人不愿意了解算法,我亦如此。
|