完美解决html2canvas + jsPDF导出pdf分页echarts内容截断问题
想直接看解决方案的可跳过我的絮絮叨叨
有段时间没有更新内容了,一方面是自己在沉淀,二是前段时间学着剪vlog想着把自己上下班及中午锻炼的碎片整理出来发到网上,咱也做一个自媒体博主,实现时间自由,财富自由,走上巅峰,登上福布斯,弹劾小日子,哈哈哈,当然有想法是好的,别管最后咋样先动起来,在行动的这个过程中总会有意想不到的收获,沉淀的过程中我也尝试着写过一些无厘头的文,巴拉巴拉我在说什么,先搞正事,完事在絮叨。
事件起因
像往常一样,我在霹雳吧啦的敲着26个字母,产品大佬过来说,小帅咱们客户反映线上导出的数据统计有问题,我想不对啊,数据有问题?不可能吧,数据有问题,应该是去找后端吧,找我干啥,是需要我跟进这个问题嘛?原来是历史问题啊~!页面数据涉及到 柱状图、饼状图、折线图和一些数据的展示呈现出现了中间断裂/截断问题,导致导出的pdf格式打印出来不美观,影响用户体验。
简单分析
- html2canvas + jsPDF现状导出pdf是一个整体,以a4的高度进行分页,问题的主要原因
- 需要对页面元素进行计算,1 + 2大于 a4的高度就另起一页 简单说干就干
- 打开百度一搜,why?为啥都没有完美的解决方法,倔友的一些方法也都试了,多少都存在问题不能解决。得,还是得自己搞。
核心代码 代码经过测试 可直接使用
我知道大家进来都想直接找解决问题的方法,因为我带着问题去找答案也一样,先解决了再听他们絮叨。上才艺展示,如果能帮到你请回来看我絮叨。
import html2Canvas from 'html2canvas'
import { jsPDF } from 'jspdf'
// pdfDom 页面dom , spacingHeight 留白间距 fileName 文件名
export function html2Pdf(pdfDom,spacingHeight,fileName){
// 获取元素的高度
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);
console.log(elementHeight, '我是页面上的elementHeight'); // 检查
// 检查添加这个元素后的总高度是否超过 A4 纸的高度
if (currentPageHeight + elementHeight > A4_HEIGHT) {
// 如果超过了,创建一个新的页面,并将这个元素添加到新的页面上
currentPageHeight = elementHeight;
element.classList.add(newPage);
console.log(element, '我是相加高度大于A4纸的元素');
}
currentPageHeight += elementHeight
}
// 根据 A4 的宽高等比计算 dom 页面对应的高度
const pageWidth = pdfDom.offsetWidth;
const pageHeight = (pageWidth / A4_WIDTH) * A4_HEIGHT;
// 将所有不允许被截断的子元素进行处理
const wholeNodes = pdfDom.querySelectorAll(`.${newPage}`);
console.log(wholeNodes, '将所有不允许被截断的子元素进行处理')
// 插入空白块的总高度
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 = 'spacing-node';
newBlock.style.background = '#fff';
// 计算空白块的高度,可以适当留出空间,根据自己需求而定
const _H = topPageNum * pageHeight - wholeNodes[i].offsetTop;
newBlock.style.height = _H + spacingHeight + 'px';
// 插入空白块
wholeNodes[i].parentNode.insertBefore(newBlock, wholeNodes[i]);
// 更新插入空白块的总高度
allEmptyNodeHeight = allEmptyNodeHeight + _H + spacingHeight;
}
}
pdfDom.setAttribute(
'style',
`height: ${domScrollHeight + allEmptyNodeHeight}px; width: ${domScrollWidth}px;`,
);
}
以上我们就完成 dom 层面的分页,下面就进入常规操作转为图片进行处理
return html2Canvas(pdfDom, {
width: pdfDom.offsetWidth,
height: pdfDom.offsetHeight,
useCORS: true,
allowTaint: true,
scale: 3,
}).then(canvas => {
// dom 已经转换为 canvas 对象,可以将插入的空白块删除了
const spacingNodes = pdfDom.querySelectorAll('.spacing-node');
for (let i = 0; i < spacingNodes.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`);
}).catch(err => {
console.log(err);
}
);
})
到这儿 htmlToPdf.js这个文件逻辑就处理完毕了,页面引入就可以正常使用了。
import { html2Pdf } from '@/utils/htmlToPdf'
// this.$refs 或 id
html2Pdf(this.$refs.viewReportCon)
如果能帮到你那最好不过了,最近天气回暖,换季期间干燥,多方因素易发生感冒,请各位 彦祖 务必保重身体。
作者:攀登的牵牛花
来源:juejin.cn/post/7346808829298262050
来源:juejin.cn/post/7346808829298262050