前端原生js实现pdf打印功能
常用方法介绍
html2canvas + jsPDF
通过html2canvas和jsPDF这两个插件可以将你想要的内容导出为pdf文件
优点:导出过程用户没有感知,对用户比较友好
缺点:画面截断,因为本质是将html导出为canvas图像,所以导出的时候会在你设定的高度位置进行暴力截断,如果你想要导出的内容格式是固定的,那可以通过一些算法规避这个问题,但是如果导出的内容格式是未知的,那么这个问题无法避免。
window.print
通过浏览器自带的打印功能可以导出pdf格式的文件,打印范围为当前页面所有内容,无法指定打印内容
优点:自动计算元素高度规避画面截断问题,可以通过css属性控制打印样式,可选式配置
缺点:使用该功能需要弹出系统弹窗让用户点击
导出pdf功能
分析:需要保证画面不被截断,而且所使用的场景中,需要导出的内容是不固定的,并且需要导出指定的内容;所以选择了window.print作为核心思路进行开发
思路:打开新窗口,将所需要导出的内容放到新窗口中,再调用window.print进行导出,导出成功之后关闭新窗口
/**
* 打印方法
* 效果:打开新页面 调用系统打印方法,打印之后会关闭新打开的页面
* ele: 需要打印的元素
*/
export const printPDF = (ele: Element) => {
if (!ele) {
throw new Error('ele is null or undefined');
}
setTimeout(() => {
// 当前项目需要配置一个打印的路由,对应空白页面即可
const newWindow = window.open('/print', '_blank');
newWindow.onload = () => {
// 通过MutationObserver监听dom元素是否渲染完成
const targetNode = newWindow.document.body;
const config = { childList: true, subtree: true };
// 所监听元素发生变化时的回调方法
const callback = function (mutationsList, observer) {
// 关闭监听
observer.disconnect();
// 使用定时的原因是因为对于link类型的css需要请求时间,不加定时会出现样式丢失的问题
setTimeout(() => {
newWindow.print();
}, 1000);
};
const observer = new MutationObserver(callback);
// 开启监听
observer.observe(targetNode, config);
// 将当前页面的所有的样式内容拷贝至新页面,否则样式会丢失
const headStr = document.head.innerHTML;
// 将新页面的body内容替换为需要打印的元素
const bodyInnerHTML = ele.outerHTML;
// 匹配当前页面的style以及link标签
const regex = /<link(.*?)>|<style(.*?)<\/style>/gs;
const marpStyles = headStr.match(regex)?.join('') || '';
newWindow.document.head.innerHTML =
newWindow.document.head.innerHTML + marpStyles;
newWindow.document.body.innerHTML = bodyInnerHTML;
newWindow.document.body.id = 'print';
newWindow.addEventListener('afterprint', () => {
// 用户关闭打印窗口时触发(取消或者导出都会触发当前事件)
// 火狐浏览器对于该事件存在问题
newWindow.close();
});
};
});
};
注意问题
1、打印样式处理
@page {
size: auto !important;
margin: 10mm !important;
}
@media print {
body {
// 不加这部分会不显示背景
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
-moz-print-color-adjust: exact;
-ms-print-color-adjust: exact;
}
// 避免图片显示不全
img {
max-width: 100% !important;
max-height: 100vh !important;
}
}
2、为什么需要加一个print的空白页面路由
在vue/react项目中,不加页面路由,打开的地址为 about:blank, 会导致静态资源的前缀为该路径,产生的现象就是静态资源url获取错误,不显示或者显示获取失败
3、图片不显示 图片资源可能太大,导致加载事件有点久,打印窗口弹出的时候还没加载完成;
可以将定时器时间设置的长一点;
或者修改一下MutationObserver的callback,将其中的setTimeout修改为通过传入的event进行判断是否打印, 因为nextjs在本地开发的时候和部署到服务器上接收到的事件不同,所以这边才会退而求其次的选择使用定时器;个人觉得使用条件判断是最优解;