实现前端页面局部转 pdf 及 打印 加分页防止内容截断
再次强调实践出真知,新项目迭代涉及到一个管理报告的下载及打印,但是项目中以往的这种功能实现主要核心在后端,如pdf 的生成,打印也是有现成的文件地址等,但这次的报告中内容比较复杂,有表格,有几个echars 数据,以及长篇的描述,后端表示这个由后端生成pdf 不太现实,因此主要任务就留在了前端。核心库html2Canvasjspdfprint-js实现思路1、html2Canvas可以将特定容器标
再次强调实践出真知,新项目迭代涉及到一个管理报告的下载及打印,但是项目中以往的这种功能实现主要核心在后端,如pdf 的生成,打印也是有现成的文件地址等,但这次的报告中内容比较复杂,有表格,有几个echars 数据,以及长篇的描述,后端表示这个由后端生成pdf 不太现实,因此主要任务就留在了前端。
核心库
- html2Canvas
- jspdf
- print-js
实现思路
1、html2Canvas 可以将特定容器标签的内容转换为图片,通过特定API 转换为对应的base64 格式的数据
2、jspdf 库 调用核心API addImage() 及 save() 方法可以将base64 的数据转换为pdf 保存到本地
3、print-js 打印的核心库,直接传入数据实现打印,不需要再重新刷新页面
重点思考 !!!分页内容防截断
由于要转pdf 和 打印 的管理报告不是一页范围内的,因此需要考虑分页,关于分页转pdf 有很多分享,可是忽略了一点,分页的时候一定不能把有些内容给截断 ,比如一个表格,一个图片,或者报告中的echars 图标等,怎么办?刚开始一点思路没有后,后来简书上看了一篇文章打开了思路。 https://www.jianshu.com/p/9f6ebc0e0d70 ,但最后没有采用,因为这个思路还是有点绕,而且需要基本所有内容标签都加上对应防截断tag,其实是没必要的。
解决思路!!!!
使用html2Canvas + jspdf 实现前端页面转pdf ,改变pdf 分页格式的唯一思路是 先改变转 pdf 的 dom (因为我在没有了解清楚的时候也在,dom 转换后pdf 分页的时候动过心思,不成功因为一切都是基于一个完整的base64 格式的 image 数据)
将确保不被分割的dom 元素 加上特定的tag 标签,判断此dom 元素的最上面和最下面是否在同一页中,如果不在说名不处理就会被截断,怎么处理,不能被截断的元素上方插入对应的空白快占位,已达到将当前元素放到下一页的目的(当前页面高度 - dom元素最上方位置 = 需要插入空白块的高度)
关于局部分页打印
正常将dom 元素按照A4 纸的标准进行分页后,dom 转pdf 的数据在打印时可以自动分页,万万没想到,预览状态下打印的一页内容,比设计的一页内容多,导致打印的时候元素又被截断,分析的原因是电脑里A4纸的标准和我设定的不一样,我设定的高度不够,因此试着调试了一下高度,解决了,还有待多个电脑测试。
核心代码
思路很早就明确了,我认为这种方法一定可以成功,万万没想到,写错了一个数据,导致忙活了一个周,当然这一个周也开发了别的功能,当我动不动就会调试一下下载 打印,因为这块经理也不确定前端能不能做好,所以就放得比较宽,想知道529 和 592 之间的差距有多远吗,就是我一周的时间和几乎怀疑自己的焦虑,因为我认为思路完全没问题的,知道今天其他功能完成我准备好好顺顺代码,结果就是一个数字写的问题。
convertPdf(isPrint) {
let title = "你想要的保存pdf文件的名字"
// 如果这个页面有左右移动,canvas 也要做响应的移动,不然会出现canvas 内容不全
const xOffset = window.pageXOffset
// 避免笔下误 灯下黑 统一写
const A4_WIDTH = 592.28
// const A4_HEIGHT = 841.89
const A4_HEIGHT = 880
let printDom = document.querySelector('#printBox')
// 根据A4的宽高计算DOM页面一页应该对应的高度
let pageHeight = printDom.offsetWidth / A4_WIDTH * A4_HEIGHT
// 将所有不允许被截断的元素进行处理
let wholeNodes = document.querySelectorAll('.whole-node')
for (let i = 0; i < wholeNodes.length; i++) {
//1、 判断当前的不可分页元素是否在两页显示
const topPageNum = Math.ceil((wholeNodes[i].offsetTop) / pageHeight)
const bottomPageNum = Math.ceil((wholeNodes[i].offsetTop + wholeNodes[i] + offsetHeight) / pageHeight)
if (topPageNum !== bottomPageNum) {
//说明该dom会被截断
// 2、插入空白块使被截断元素下移
let divParent = wholeNodes[i].parentNode
let newBlock = document.createElement('div')
newBlock.className = 'emptyDiv'
newBlock.style.background = '#fff'
// 3、计算插入空白块的高度 可以适当流出空间使得内容太靠边,根据自己需求而定
let _H = topPageNum * pageHeight - wholeNodes[i].offsetTop
newBlock.style.height = _H + 30 + 'px'
divParent.insertBefore(newBlock, wholeNodes[i])
}
// 以上完成dom层面的分页 可以转为图片进一步处理了
html2Canvas(printDom, { height: printDom.offsetHeight, width: printDom.offsetWidth, scrollX: -xOffset, allowTaint: true }).then(canvas => {
//dom 已经转换为canvas 对象,可以将插入的空白块删除了
let emptyDivs = document.querySelectorAll('.emptyDiv')
for (let i = 0; i < emptyDivs.length; i++) {
emptyDivs[i].parentNode.removeChild(emptyDivs[i])
}
// 有一点重复的代码
let contentWidth = canvas.width
let contentHeight = canvas.height
let pageHeight = contentWidth / A4_WIDTH * A4_HEIGHT
let leftHeight = contentHeight
let position = 0
let imgWidth = A4_WIDTH
let imgHeight = A4_WIDTH / contentWidth * contentHeight
let pageData = canvas.toDataURL('image/jpeg', 1.0)
if (isPrint) {
//如果是打印,可以拿着分号页的数据 直接使用
printJs({ printable: pageData, type: 'image', base64: true, documentTitle: '\u200E' })
return
}
//计算分页的pdf
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 -= A4_HEIGHT
if (leftHeight > 0) {
PDF.addPage()
}
}
}
PDF.save(title + '.pdf')
})
}
},
dom 元素要做的处理就是添加对应的class 标记就行了。
如果有更好的建议,欢迎我们一起讨论 解决,比如打印的时候动态分页,有没有更优办法。
更多推荐
所有评论(0)