前言:这段时间在项目开发中,遇到这样一个需求:前端在一个页面中使用echarts生成了几个图表,需要可以将他们导出为一个pdf。虽然echarts自身有下载,但只能 单个图表下载为图片,所以不满需求。
根据调研,决定采用 html2canvas 和 jspdf 的方式实现,即使用 html2canvas 将页面转成图片后,放到pdf中导出。
一、安装插件与引入
html2canvas:npm install html2canvas --save-dev
import html2canvas from 'html2canvas';
jspdf:npm install jspdf --save-dev
import jsPDF from 'jspdf';
二、核心js ( mainBox 为要导出的盒子id )
html2canvas(document.getElementById('mainBox'),{
dpi: 200,//导出pdf清晰度
useCORS: true // 【重要】开启跨域配置
}).then(function(canvas) {
// document.body.appendChild(canvas);
let contentWidth = canvas.width;
let contentHeight = canvas.height;
//一页pdf显示html页面生成的canvas高度;
let pageHeight = contentWidth / 592.28 * 841.89;
//未生成pdf的html页面高度
let leftHeight = contentHeight;
//pdf页面偏移
let position = 0;
//html页面生成的canvas在pdf中图片的宽高(a4纸的尺寸[595.28,841.89])
let imgWidth = 595.28;
let imgHeight = 592.28 / contentWidth * contentHeight;
let pageData = canvas.toDataURL('image/jpeg', 1);
let 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 > 0) {
pdf.addPage();
}
}
}
pdf.save('TestReport.pdf');
});
三、我踩过的坑
1. html2canvas 截图只能接到当前屏幕的,滚动到屏幕以外的截取不到?
解决方法:请注意如果内容高度超过了屏幕高度,请确认是否有滚动条,我就是因为项目对滚动条做了特殊处理,不会显示出来,滚动是自己封装实现的,导致了截图不全的问题。
还有一种情况,我将下载按钮放在要导出的盒子下方,会导致截图不全,但我将按钮放在盒子上方就可以,没找到原因....
html2canvas 和 jspdf 这个方法网上有很多现实文章,可自行搜索参考。特别是 html2canvas 的用法,有坑,使用时请注意
使用场景:导出页面,页面中有大量图片,文字以及附件的展示(附件内容不管) =》 类似一张试卷
遇到的问题:
1. 截图后图片不显示 => 原因:在html2canvas里面,是使用脚本去操作的,也就是说使用脚本把html转换成canvas,但是有一个限制,那就是不能使用跨源的图片。而 img 标签本身可以避免跨域问题,所以显示没有问题。
后期处理跨域即可
2. 多行输入框 textarea 截图后内容不换行
将 textarea 换成普通div即可,并按 textarea 完善样式
3. 内容被断行
这个需要计算一张纸能显示多少内容并且换页即可
处理换页完整逻辑:
// --------------------------- 方法一 ---------------------------
// 导出内容
handleExport() {
this.exportLoading = true;
// 定义A4 纸的宽高
const A4_WIDTH = 595.28;
const A4_HEIGHT = 841.89;
this.$nextTick(() => {
// 获取到截图dom
const target = this.$refs.mainBox;
// 以A4纸的大小去计算出 一页pdf能显示高 => 向上取整 (两种方式计算的结果是一样的,下面的更好记录 页面宽/页面高 = A4宽 / A4 高)
// let pageHeight = Math.ceil(target.scrollWidth / A4_WIDTH * A4_HEIGHT);
let pageHeight = Math.ceil(target.scrollWidth / (A4_WIDTH / A4_HEIGHT));
// 获取分割dom,此处为class类名为question-item的dom
let lableListID = target.getElementsByClassName('print-item');
// 进行分割操作,当dom内容已超出a4的高度,则将该dom前插入一个空dom,把他挤下去,分割
for (let i = 0; i < lableListID.length; i++) {
/*
* offsetTop 是当前元素距离弹窗顶部的距离 => 这个偏移量是内容到dialog顶部(包含了dialog header);
* offsetHeight 是当前元素本身的高度,包含内边距(padding)和边框(border)
* 当前元素高度+距离顶部的偏移量 / 一页显示的高度 取整 => 得到的是该dom应该放在第几页
*/
let multiple = Math.ceil((lableListID[i].offsetTop + lableListID[i].offsetHeight) / pageHeight);
if (this.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);
// 插入元素的高度 = pdf这一页剩余空间 + 65(dialog header的高度,需要减去) + 50 (保证下一页顶部有一点padding)
newNode.style.height = _H + 65 + 50 + 'px';
newNode.style.width = '100%';
let next = lableListID[i].nextSibling; // 获取div的下一个兄弟节点
// 判断兄弟节点是否存在
if (next) {
// 存在则将新节点插入到div的下一个兄弟节点之前,即div之后
divParent.insertBefore(newNode, next);
} else {
// 不存在则直接添加到最后,appendChild默认添加到divParent的最后
divParent.appendChild(newNode);
}
}
}
this.pdf();
});
},
// --------------------------- 方法二 ---------------------------
// 判断是否需要添加空白div
isSplit (nodes, index, pageHeight) {
// 计算当前这块dom是否跨越了a4大小,以此分割 => 当前这个dom在前一页(小于一页显示高度pageHeight),但下一个dom在下一页了(大于一页显示高度pageHeight)
if (nodes[index].offsetTop + nodes[index].offsetHeight < pageHeight && nodes[index + 1] && nodes[index + 1].offsetTop + nodes[index + 1].offsetHeight > pageHeight) {
return true;
}
return false;
},
// --------------------------- 方法三 ---------------------------
pdf() {
const JsPDF = jsPDF;
setTimeout(() => {
html2canvas(this.$refs.mainBox, {
dpi: 200, //导出pdf清晰度
useCORS: true // 【重要】开启跨域配置
}).then(canvas => {
// document.body.appendChild(canvas);
let contentWidth = canvas.width;
let contentHeight = canvas.height;
//一页pdf显示html页面生成的canvas高度;
let pageHeight = contentWidth / 595.28 * 841.89;
//未生成pdf的html页面高度
let leftHeight = contentHeight;
//pdf页面偏移
let position = 0;
//html页面生成的canvas在pdf中图片的宽高(a4纸的尺寸[595.28,841.89])
let imgWidth = 595.28;
let imgHeight = 595.28 / contentWidth * contentHeight;
let pageData = canvas.toDataURL('image/jpeg', 1);
let 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 > 0) {
pdf.addPage();
}
}
}
pdf.save(`TestReport.pdf`);
this.exportLoading = true;
this.dialogVisible = false;
});
}, 300)
},
官方文档:html2canvas:Getting Started | html2canvas
jspdf:jspdf - npm
参考博客:https://www.jianshu.com/p/651c40d565e4
文章仅为本人学习过程的一个记录,仅供参考,如有问题,欢迎指出
对博客文章的参考,若原文章博主介意,请联系删除!请原谅