前端原生js实现pdf打印功能

344 阅读3分钟

前端原生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的空白页面路由

image.png

在vue/react项目中,不加页面路由,打开的地址为 about:blank, 会导致静态资源的前缀为该路径,产生的现象就是静态资源url获取错误,不显示或者显示获取失败

3、图片不显示 图片资源可能太大,导致加载事件有点久,打印窗口弹出的时候还没加载完成;

可以将定时器时间设置的长一点;

或者修改一下MutationObserver的callback,将其中的setTimeout修改为通过传入的event进行判断是否打印, 因为nextjs在本地开发的时候和部署到服务器上接收到的事件不同,所以这边才会退而求其次的选择使用定时器;个人觉得使用条件判断是最优解;