前端实现 HTML 转 PDF 并导出 - zptime/blog GitHub Wiki

主要介绍了前端实现 HTML 转 PDF 并导出的一些方法,包括浏览器自带的、vue-print-nb、html2canvas和jspdf,wkhtmltopdf以及iText

系统中使用了图形化报表,那么导出报表也是必不可少了,一般的导出形式就是pdf文件,对此,进行了一番研究,以下就是踩坑经验。

以如下报表页为例:

打印效果

Window.print

官网地址:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/print

详细使用可参考文档:window.print() 前端实现网页打印详解

这个是借助浏览器自带的打印来实现的,默认打印页面中 body里的所有内容,即全局打印,如果要局部打印,需要替换 body 的内容,会改变原网页内容,体验不是很好。

 <a-button type="primary" @click="window.print()">window.print</a-button>

打印效果

vue-print-nb

官网安装及使用文档:https://www.npmjs.com/package/vue-print-nb

该插件本质上也是使用浏览器自带的打印 window.print()来实现的,默认会创建一个 iframe,将要打印的内容嵌入进去。相较第一个方法而言,更加灵活一点,但是样式的支持还是比较差的,ant design vue 组件的样式展示不全,需要自己去重写样式,这样就麻烦了呀。

// 1. 安装
npm install vue-print-nb --save

// 2. 全局注册(main.js)
import Print from "vue-print-nb";
Vue.use(Print);

// 3. 使用
<a-button type="primary" v-print="printObj">
  vue-print-nb
</a-button>;

var printObj = {
  id: "pdfDom",
  popTitle: "good print",
  extraCss: "https://www.google.com,https://www.google.com",
  extraHead: '<meta http-equiv="Content-Language"content="zh-cn"/>',
};

打印效果

html2canvas + jspdf

html2canvas:https://github.com/niklasvh/html2canvas

jspdf:https://github.com/parallax/jsPDF

通过 html2canvasHTML 页面转换成canvas图片,再通过 jspdf 将图片转成 pdf格式文件。

缺点:

  1. 清晰度不够
  2. 分页时,内容会被截断,不能智能展示完整
  3. 页面内容太长时(宽高限制为 14400),无法打印出内容,一直是空白页。这个真的就很不友好了呀,报表的内容肯定多呀...

打印效果

注意点:

  • 引入外链图片时,需要配置图片跨域,给 img 标签设置 crossOrigin='anonymous'
  • 提高生成图片质量,可以适当放大 canvas 画布,通过设置 scale 缩放画布大小,或者设置 dpi 提高清晰度。
  1. 安装
npm install html2canvas jspdf --save
  1. 定义工具函数
// utils/htmlToPdf.js:导出页面为PDF格式
import html2Canvas from "html2canvas";
import JsPDF from "jspdf";

export default {
  install(Vue, options) {
    // id-导出pdf的div容器;title-导出文件标题
    Vue.prototype.htmlToPdf = (id, title) => {
      const element = document.getElementById(`${id}`);
      const opts = {
        scale: 12, // 缩放比例,提高生成图片清晰度
        useCORS: true, // 允许加载跨域的图片
        allowTaint: false, // 允许图片跨域,和 useCORS 二者不可共同使用
        tainttest: true, // 检测每张图片已经加载完成
        logging: true, // 日志开关,发布的时候记得改成 false
      };

      html2Canvas(element, opts)
        .then((canvas) => {
          let contentWidth = canvas.width;
          let contentHeight = canvas.height;
          // 一页pdf显示html页面生成的canvas高度;
          let pageHeight = (contentWidth / 592.28) * 841.89;
          // 未生成pdf的html页面高度
          let leftHeight = contentHeight;
          // 页面偏移
          let position = 0;
          // a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
          let imgWidth = 595.28;
          let imgHeight = (592.28 / contentWidth) * contentHeight;
          let pageData = canvas.toDataURL("image/jpeg", 1.0);

          // a4纸纵向,一般默认使用;new JsPDF('landscape'); 横向页面
          let PDF = new JsPDF("", "pt", "a4");

          // 当内容未超过pdf一页显示的范围,无需分页
          if (leftHeight < pageHeight) {
            // addImage(pageData, 'JPEG', 左,上,宽度,高度)设置
            PDF.addImage(pageData, "JPEG", 0, 0, imgWidth, imgHeight);
          } else {
            // 超过一页时,分页打印(每页高度841.89)
            while (leftHeight > 0) {
              PDF.addImage(pageData, "JPEG", 0, position, imgWidth, imgHeight);
              leftHeight -= pageHeight;
              position -= 841.89;
              if (leftHeight > 0) {
                PDF.addPage();
              }
            }
          }
          PDF.save(title + ".pdf");
        })
        .catch((error) => {
          console.log("打印失败", error);
        });
    };
  },
};
  1. 全局注册
// main.js文件
import htmlToPdf from "./utils/htmlToPdf";
Vue.use(htmlToPdf);
  1. 使用
export default {
  methods: {
    handleExportPdf() {
      // 滚动到顶部,确保打印内容完整
      document.body.scrollTop = 0; // IE的
      document.documentElement.scrollTop = 0; // 其他
      this.htmlToPdf("pdfDom", "统计报告");
    },
  },
};

打印效果

wkhtmltopdf

wkhtmltopdf 使用 webkit 渲染引擎开发的,直接把 html 页面转成 pdf,但是需要安装在服务器上。

官网地址:https://wkhtmltopdf.org/

官方 Issues:https://github.com/wkhtmltopdf/wkhtmltopdf/issues

配置参数:https://wkhtmltopdf.org/usage/wkhtmltopdf.txt

配置参数中文参考:https://segmentfault.com/a/1190000018988358

// 1. 安装:在官网下载安装包

// 2. 查看版本号:wkhtmltopdf 0.12.6 (with patched qt)
$ wkhtmltopdf -V

// 3. 测试:在cmd里输入测试指令,查看处理进度
$ wkhtmltopdf https://www.baidu.com/ ~/Desktop/baidu.pdf // 网络页面

$ wkhtmltopdf --javascript-delay 3000 report.html test.pdf // 本地HTML页面

百度页测试指令,可查看处理进度:

打印效果

百度页导出效果展示: 打印效果

前端需要写静态html模版页面,交给服务端渲染并且导出。

模版页实践踩坑:

  1. 采用 webkit 渲染引擎开发,对 CSS 样式整体支持友好,但对 CSS3 的新特性支持不太好,部分页面样式会失效。 wkhtmltopdf uses Qt to render HTML. The current version of wkhtmltopdf uses Qt 4.8.5, which uses version 2.2.4 of WebKit, As you can see I tried using the -webkit- prefixes(wkhtmltopdf 使用 Qt 来呈现 HTML。 wkhtmltopdf 的当前版本使用 Qt 4.8.5,它使用 WebKit 的 2.2.4 版,建议使用 -webkit- 前缀)
  2. 采用 flex 布局,需要使用兼容性样式,具体可查看issue
  3. 采用 flex 布局,导出的 pdf 会被截断,因此改为 table 布局
  4. 默认字体时宋体,如果机器上没有,中文无法显示;要想使用别的字体,必须安装对应的字体,具体可查看issue
  5. vertical-align 属性不起作用
  6. 静态模板写 CSS 样式比较麻烦,可以直接写 SCSS,然后同步转化成 CSS
// 安装sass
npm install -g sass

// 实时编译命令
sass --watch test.scss:test.css

iText

IText:用于 Java 后台,将 HTML 文件转换成 PDF 文件。基于 java 将 html 的 css 样式做解析处理,仅对较简单的页面和样式支持。

使用流程:前端负责完成静态模版页面,后端基于模版页进行数据填充,然后导出 pdf 文件,前端进行下载。

模版页实践踩坑:

  1. 对 CSS 样式支持极差,CSS3 属性都不支持
  2. 不能用 flex 布局,基本只能用 table 实现
  3. 要换页的地方,添加page-break-after: always;,表示下一个元素将会换页,转 pdf 时 itext 会自动识别
  4. 定位中,不支持以百分比为单位的 css 属性 top(css property top in percents is not supported)
  5. 绝对定位可能报错(Occupied area has not been initialized. Absolute positioning might be applied incorrectly)
⚠️ **GitHub.com Fallback** ⚠️