前言
这两天公司让做一个需求,前端有一个页面,类似于报告页,需要做导出操作,导出来的文件为PDF格式。另外还有一个需求就是,让报告先上传,再导出下载。于是我查阅了一些资料,总结了一下如何完成,分享一下。
难点
- 跨域问题:由于浏览器的安全策略,HTML2Canvas 在跨域场景下可能会受到限制。如果要捕获或渲染来自不同域的内容,可能需要通过 CORS(跨域资源共享)或代理服务器来解决跨域问题。
- 对复杂页面的支持:HTML2Canvas 非常适用于简单的静态页面,但对于复杂的动态页面,如包含大量 JavaScript 动画或通过 AJAX 更新的内容,它可能无法完全捕获和呈现所有元素。
- 包含外部资源:如果 HTML 元素引用了外部资源,例如图片、字体或 CSS 样式文件等,HTML2Canvas 可能不能直接捕获这些资源并合成成图像。需要特殊处理来确保这些资源正确加载和呈现。
- 存在图片、组件、文字、表格被分割的现象,需要分页处理
处理思路
- 监听图片加载事件:使用 JavaScript 监听图片加载事件,确保图片完全加载后再进行截图操作,可以通过添加 onload 事件对图片进行处理。
- 手动处理动态和异步内容:当页面中有动态或异步获取的内容时,可以在相关操作完成后手动调用html2canvas来实现截图操作。
- 调整CSS样式:根据html2canvas的支持情况,调整CSS样式以适应截图需求,并考虑使用其他工具或技术来增强效果。
官网链接
官方文档链接 addImage - Documentation (artskydj.github.io)
代码展示
- 首选需要下载两个依赖插件
npm install html2canvas --save
npm install jsPDF --save
- 接着写一个js文件,导出所封装的方法,具体方法如下
// 页面导出为pdf格式 //title表示为下载的标题,html表示document.querySelector('#myPrintHtml')
import html2Canvas from 'html2canvas'
import JsPDF from 'jspdf'
const htmlPdf = {
getPdf(title, html) {
html2canvas(html, {
allowTaint: true,
useCORS: true,
dpi: window.devicePixelRatio * 4, // 将分辨率提高到特定的DPI 提高四倍
background: '#FFFFFF',
}).then(canvas => {
//未生成pdf的html页面高度
var leftHeight = canvas.height
var a4Width = 595.28
var a4Height = 841.89 //A4大小,210mm x 297mm,四边各保留10mm的边距,显示区域190x277
//一页pdf显示html页面生成的canvas高度;
var a4HeightRef = Math.floor((canvas.width / a4Width) * a4Height)
//pdf页面偏移
var position = 0
var pageData = canvas.toDataURL('image/jpeg', 1.0)
var pdf = new jsPDF('p', 'pt', 'a4') //A4纸,纵向
var index = 1,
canvas1 = document.createElement('canvas'),
height
pdf.setDisplayMode('fullwidth', 'continuous', 'FullScreen')
var pdfName = title
function createImpl(canvas) {
console.log(leftHeight, a4HeightRef)
if (leftHeight > 0) {
index++
var checkCount = 0
if (leftHeight > a4HeightRef) {
var i = position + a4HeightRef
for (i = position + a4HeightRef; i >= position; i--) {
var isWrite = true
for (var j = 0; j < canvas.width; j++) {
var c = canvas.getContext('2d').getImageData(j, i, 1, 1).data
if (c[0] != 0xff || c[1] != 0xff || c[2] != 0xff) {
isWrite = false
break
}
}
if (isWrite) {
checkCount++
if (checkCount >= 10) {
break
}
} else {
checkCount = 0
}
}
height = Math.round(i - position) || Math.min(leftHeight, a4HeightRef)
if (height <= 0) {
height = a4HeightRef
}
} else {
height = leftHeight
}
canvas1.width = canvas.width
canvas1.height = height
console.log(index, 'height:', height, 'pos', position)
var ctx = canvas1.getContext('2d')
ctx.drawImage(
canvas,
0,
position,
canvas.width,
height,
0,
0,
canvas.width,
height,
)
var pageHeight = Math.round((a4Width / canvas.width) * height)
// pdf.setPageSize(null, pageHeight)
if (position != 0) {
pdf.addPage()
}
pdf.addImage(
canvas1.toDataURL('image/jpeg', 1.0),
'JPEG',
10,
10,
a4Width,
(a4Width / canvas1.width) * height,
)
leftHeight -= height
position += height
if (leftHeight > 0) {
setTimeout(createImpl, 500, canvas)
} else {
pdf.save(title + '.pdf')
}
}
}
//当内容未超过pdf一页显示的范围,无需分页
if (leftHeight < a4HeightRef) {
pdf.addImage(
pageData,
'JPEG',
0,
0,
a4Width,
(a4Width / canvas.width) * leftHeight,
)
pdf.save(title + '.pdf')
} else {
try {
pdf.deletePage(0)
setTimeout(createImpl, 500, canvas)
} catch (err) {
// console.log(err);
}
}
})
}
}
export default htmlPdf
- 在vue文件中引入所封装的js
import htmlPdf from './html2pdf' // 这个根据自己所写的文件位置来引入
// 比如写了一个下载的方法 handleDownLoad
handleDownLoad(){
htmlPdf.getPdf('导出pdf名字', document.querySelector('#print-zone'))
}
// #print-zone 是你想导出的dom节点
// 注意的是,页面有display:none; 的是无法导出来的。
到此位置,一个页面导出PDF就写好了。
下面讲,如何将文件先上传再下载。
文件先上传后导出下载
分析
在我们用插件的时候,为什么可以导出下载,实际上也是因为插件本身将文件先转化为了流,然后才能下载。那么顺着这个思路去写上传,也就是先将文件转化为流,再转化为二进制,再通过formData格式走上传接口,拿到返回的url。
要使用html2canvas将文件转换为二进制流,可以按照以下步骤进行操作:
首先,确保你已经引入了html2canvas库文件到你的项目中。
使用html2canvas库,将需要转换的HTML元素渲染为canvas对象。例如,如果你想转换整个页面,可以使用如下代码:
html2canvas(document.body).then(function(canvas) {
// 在这里执行后续操作
});
完成渲染后,可以使用canvas对象的toDataURL()方法将图片转换为Base64编码的数据URL。
var dataURL = canvas.toDataURL("image/png");
最后,从Base64编码的数据URL中提取出二进制数据流。可以使用window.atob()和Blob构造函数来实现这一点。
var base64Data = dataURL.split(',')[1]; // 忽略前缀部分 'data:image/png;base64,'
var binaryData = window.atob(base64Data);
var arrayBuffer = new ArrayBuffer(binaryData.length);
var uintArray = new Uint8Array(arrayBuffer);
for (var i = 0; i < binaryData.length; i++) {
uintArray[i] = binaryData.charCodeAt(i);
}
var blob = new Blob([uintArray], {type: 'image/png'});
let formData = new formData()
formData.append('file',blob)
// 将 formData 放到接口里面传进去 ,在上传接口返回里面,
// 如果上传成功,则执行刚刚导出pdf的方法,这样就完成了一个先上传后下载的需求
现在,你可以使用blob变量来处理这个二进制数据流,比如上传到服务器或者进行其他操作。
请注意,这只是一个基本的示例,具体在你的项目中可能需要根据需求进行适当的修改和调整。
总结
整个导出PDF的操作,难点就在于封装导出的方法,里面做截取分页的操作,写的不是很好,希望提出宝贵建议。