概要
在实际使用中发现自定义富文本内容由于文字大小问题,格式问题,分页大小问题等,会导致导出内容在分页处有不同程度的截断现象。
主要处理的难点
图片、文字被分割的现象,即分页处理
技术细节
提示:这里可以添加技术细节
网上查询资料即可发现前端导出pdf使用html2canvas和jspdf即可实现简单pdf导出,这块不再赘述。主要讲述分页处截断问题处理的思路,假设pdf导出页面固定高度为a,插入的每个块元素的高度为b,插入的元素距离顶部的高度为c,当前页码为p。即可得到公式b+c>a*p时是分页截断处,此时的块元素将会有被从中间截断的风险。以下称这个有截断风险的元素为截断元素。此时有两种方案:
方案一: 核心:在页面无法放入截断元素时将页面用空白元素补空,并将截断元素挤下去
。在截断元素前插入a+p-c高度的空白元素。将截断元素顶到下一页的开始处。此方法如果遇到纯文字的极端情况会有比较大的留白,但是如果被截断元素是图片元素适用此方式
方案二: 核心:将截断元素从中间分开,然后在分页处插入空白元素
。在截断元素处再插入一个截断元素,并且根据a+p-c的高度,以及此截断元素的行高计算,比如行高为36,就设置将页面剩余高度(a+p-c)分开两部分,第一部分为Math.floor((a+p-c)/36)*36 (n为整数),第二部分为剩余的高度( 剩余高度小于36,故完整放入一行文字,此处高度为文字产生截断的真正原因,将此处做空白处理,即可避免。此处也是该方案的关键
),然后将新插入的截断元素进行marginTop为- Math.floor((a+p-c)/36)*36 (n为整数)高度的元素。此方法适用整段文字的场景。分页处留白也都是小于文字行高的空白。让人更容易接受。
代码实现
方案二实际为方案一的优化。以下为方案二的核心代码
// 判断是否需要添加空白div
const isSplit = (nodes, index, pageHeight) => {
let x1 = document.getElementById('itemsss').offsetTop
let x = document.getElementById('itemss').offsetTop + x1
// 计算当前这块dom是否跨越了a4大小,以此分割
if (nodes[index].offsetTop + x + nodes[index].offsetHeight < pageHeight && nodes[index + 1] && nodes[index + 1].offsetTop + x + nodes[index + 1].offsetHeight > pageHeight) {
return true;
}
return false;
}
export const outPutPdfFn = async () => {
// $myLoading 自定义等待动画组件,实现导出事件的异步等待交互
// dom的id。
let pageHeight = 1137;
const pdfDom = document.getElementById('pdfDom') // 需要导出pdf的内容
let lableListID = pdfDom.getElementsByTagName('p'); // 可能被截断的所有块元素
// 进行分割操作,当dom内容已超出a4的高度,则将该dom前插入一个空dom,把他挤下去,分割
for (let i = 0; i < lableListID.length; i++) {
// 获取分割dom,此处为class类名为item的dom
let x1 = document.getElementById('item').offsetTop
let x2 = document.getElementById('itemss').offsetTop + x1
let multiple = Math.ceil((lableListID[i].offsetTop + x2 + lableListID[i].offsetHeight) / pageHeight);
let x = x2 - 21*(multiple - 1) // 此处因为页面高度不准加的随机数
// 判断是否该分页
if (isSplit(lableListID, i, multiple * pageHeight)) {
let divParent = lableListID[i].parentNode; // 获取该div的父节点
let newNode = document.createElement('div');
newNode.className = 'emptyDiv';
newNode.style.background = 'transparent';
newNode.style.overflow = 'hidden';
let _H = multiple * pageHeight - (lableListID[i].offsetTop + x + lableListID[i].offsetHeight);
const a = parseInt(Math.floor(_H/36))*36
newNode.style.height = _H + 'px';
newNode.style.width = '100%';
let next = lableListID[i].nextSibling; // 获取div的下一个兄弟节点
// 判断兄弟节点是否存在
if (next) {
if(a>0){
newNode.innerHTML = `<p style="height: ${a}px;overflow:hidden;margin-top:0;text-indent: ${getComputedStyle(next, false)['text-indent']};">${next.innerHTML}</p>`
console.log(next.offsetHeight, next.getAttribute('style'), getComputedStyle(next, false)['text-indent'])
next.style=`overflow:hidden;height: ${next.offsetHeight-a}px;`
next.innerHTML = `<p style="margin-top: -${a}px;text-indent: ${getComputedStyle(next, false)['text-indent']};">${next.innerHTML}</p>`
}
// 存在则将新节点插入到div的下一个兄弟节点之前,即div之后
divParent.insertBefore(newNode, next);
}
else {
// 不存在则直接添加到最后,appendChild默认添加到divParent的最后
divParent.appendChild(newNode);
}
}
}
}
export const getPdf = async (ref, footer, header, loading) => {
await outPutPdfFn(ref)
html2canvas(ref, {
allowTaint: false,
taintTest: false,
logging: false,
useCORS: true,
dpi: 4, //将分辨率提高到特定的DPI 提高四倍
scale:4 //按比例增加分辨率
}).then(async canvas=>{
var pdf = new JsPDF('p', 'mm', 'a4'); //A4纸,纵向
var ctx = canvas.getContext('2d'),
a4w = 160, a4h = 247, //A4大小,210mm x 297mm,四边各保留10mm的边距,显示区域190x277
imgHeight = Math.floor(a4h * canvas.width / a4w), //按A4显示比例换算一页图像的像素高度
renderedHeight = 0;
let i = 0
let pages = Math.ceil(canvas.height/imgHeight)
while(renderedHeight < canvas.height) {
i++
var page = document.createElement("canvas");
page.width = canvas.width;
page.height = Math.min(imgHeight, canvas.height - renderedHeight);//可能内容不足一页
//用getImageData剪裁指定区域,并画到前面创建的canvas对象中
page.getContext('2d').putImageData(ctx.getImageData(0, renderedHeight, canvas.width, Math.min(imgHeight, canvas.height - renderedHeight)), 0, 0);
pdf.addImage(page.toDataURL('image/jpeg', 1.0), 'JPEG', 25, 25, a4w, Math.min(a4h, a4w * page.height / page.width)); //添加图像到页面,保留10mm边距
if(i>1){
const canvas2 = await html2canvas(header, {
// allowTaint: true, // 允许渲染跨域图片
dpi: 0.5, //按比例增加分辨率
useCORS: true,// 允许跨域
});
pdf.addImage(canvas2.toDataURL('image/jpeg', 1.0), 'JPEG', 25, 24.9, a4w, 0.1)
}
// 此处为页脚添加
footer.querySelector('.pdf-footer-page').innerText = i;
footer.querySelector('.pdf-footer-page-count').innerText = pages;
const canvas1 = await html2canvas(footer, {
// allowTaint: true, // 允许渲染跨域图片
dpi: 4,
scale: 4, // 增加清晰度
useCORS: true,// 允许跨域
allowTaint: false,
taintTest: false,
logging: false,
useCORS: true,
});
renderedHeight += imgHeight;
pdf.addImage(canvas1.toDataURL('image/jpeg', 1.0), 'JPEG', 25, 272, a4w, 9)
if(renderedHeight < canvas.height) {
pdf.addPage();//如果后面还有内容,添加一个空页
}
// delete page;
}
//保存文件
pdf.save('测试.pdf')
loading.exportLoading = false
// 以下方法实现预览
// const blob = pdf.output("bloburl");// 转base64
// window.open(blob)
})
}