likes
comments
collection
share

DOM 转 PDF 的 5 种方案

作者站长头像
站长
· 阅读数 21

项目中想要将侧边弹窗里的内容导出成 PDF. 纯前端手段是否可以完成呢?

一分钟 DEMO

所有的 demo 都用最简便的代码实现, 可以直接控制台执行;


来玩一玩


结论简单明了

欢迎评论更多思路


方案对比

方案概要css 媒体查询iframe 打开 DOM 元素将 body 重置为目标 dom 元素, 打印后还原 body三方库 jsPDF三方库 jsPDF + 三方库 html2canvas
原理简述1. css 媒体查询 print, 可以在打印时隐藏不相关的元素, 将要打印的局部占满全屏, z-index 设置极高 2. window.print 打印可另存为 pdf 1. 创建一个空的 iframe 我们可以完全控制 window 对象和 dom 节点 2. 往 iframe 中插入想要打印的 html 模板 3. 往 iframe 中插入想要打印的 css 样式 4. 触发 iframe 的 window.print 另存为 pdf1. 将整个 body 的 dom 节点缓存下来2. 将 body 设置为打印的目标元素3. 触发 window.print 另存为 pdf4. 还原 body 的 dom 节点1. 可以创建 pdf 文件, 然后逐个元素添加至 pdf 文件中;2. 也可以将 dom 节点转换为 pdf1. html2canvas 将 dom 节点转换为图片2. jsPDF 将图片转换为 pdf 文件
优点- 常规打印样式设置, 可以用于网页 pdf 导出; 也可以稍加调整用于网页局部 pdf 导出;- 改动难度小, 不需要编写 JS 逻辑- 可以将任意内容导出成 pdf 文档, 而不必是网页上的内容- 改动非常小, 实现非常快速- 可以完全掌控 pdf 文件的构造, 逐个段落输出- 改动非常小, 实现非常快速
缺点- 没有明显缺点- 没有明显缺点- 会丢失 DOM 节点绑点的事件, 大部分场景这思路就不可用- 打印过程中, 网页内容被改变, 略微影响用户体验- 目标元素的样式如果和 DOM 层级结构相关将会丢失- 需要前端引入字体库, 否则就是乱码 (20M 左右), - 直接将 dom 节点转 pdf 效果较差, 丢失大量样式设置- 丢失 pdf 文档的文本内容, 和直接导出图片没有区别- html2canvas 库的 bug, 会造成部分样式错误渲染, 比如阴影;
demodemo 1demo 2demo 3demo 4demo 5

所有的 demo 都用最简便的代码实现, 可以直接控制台执行;

demo 1 css 媒体查询

示例网站: baidu.com

第一步: 在 element 中新增 <style> 标签, 内容如下

<style>
    .mnav.c-font-normal.c-color-t {
        color: red;
    }
    @media print {
      .mnav.c-font-normal.c-color-t {
        color: green !important; 
      }
    }
</style>

头部超链接颜色变成了红色

DOM 转 PDF 的 5 种方案

第二步: 调试控制台运行 window.print(), 打印的 PDF 中头部超链接颜色显示为绿色

DOM 转 PDF 的 5 种方案

demo 2 iframe 打开 DOM 元素

示例网站: baidu.com

调试控制台直接执行如下代码:

     function printElement(e) {
        var ifr = document.createElement('iframe');
        ifr.style = 'height: 0px; width: 0px; position: absolute'
        document.body.appendChild(ifr);
        ifr.contentDocument.body.appendChild(e.cloneNode(true))
        ifr.contentWindow.print();

        ifr.parentElement.removeChild(ifr);
    }
    var style = document.createElement('style')
    style.innerText = '* { color: red; }'
    var img = document.createElement('img')
    img.height = 300; 
    img.width = 300;
    img.src = 'https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png'
    var div = document.createElement('div')
    div.innerText = "这是百度网站"
    div.appendChild(style)
    div.appendChild(img)
    printElement(div)

DOM 转 PDF 的 5 种方案

现在我们就是想打印什么打印什么

demo 3 将 body 重置为目标 dom 元素, 打印后还原 body

示例网站: baidu.com

第一步: 调试控制台 - element 页签, 选中某一个元素

// $0 是 chrome 调试工具选中的元素
// 没有使用过的话, 可以在 Element 页签点中任意一个 HTML 标签
// 然后控制台打印看看 $0 是啥

第二步: 调试控制台运行如下代码

方案特别简单, 就不解释源码了

    // $0 是 chrome 调试工具选中的元素
    let storeNodeHtml = document.body.innerHTML 
    let printNode = $0.cloneNode(true) 
    document.body.innerHTML = '' 
    document.body.innerHTML = printNode.innerHTML 
    window.print() 
    document.body.innerHTML = storeNodeHtml

DOM 转 PDF 的 5 种方案

打印整个网页还好, 打印局部可能会丢失样式;毕竟把某个元素直接丢进 <body> 元素, 丢失了层级结构, css 选择器将会失效; 自己试试就知道了;

demo 4 三方库 jsPDF

示例网站: google.com

第一步: 运行代码加载三方库

    // 控制台执行第一段
    var jspdfScript = document.createElement('script') 
    jspdfScript.src="https://unpkg.com/jspdf@latest/dist/jspdf.umd.min.js"; 
    document.body.append(jspdfScript)
    var html2canvasScript = document.createElement('script')
    html2canvasScript.src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"; 
    document.body.append(html2canvasScript)

第二步: 调试控制台 - element 页签, 选中某一个元素, demo 直接选取 <body>

第三步: 调试控制台 - 运行如下代码

    // 等 js 加载完成, 控制台执行第二段
    // $0 是调试工具选中的元素, 没有使用过的话, 可以直接控制台打印看看 $0 是啥
    window.jsPDF = jspdf.jsPDF
    var doc = new jsPDF()
    doc.html($0, {
      callback: function(doc) {
       doc.save('sample-document.pdf');
      },
      x: 1,
      y: 1,
      width: 170,
      windowWidth: 1650
    });

DOM 转 PDF 的 5 种方案

图片变形, 中文乱码....懒得解决

demo 5 jsPDF + html2canvas

示例网站: google.com

第一步: 运行代码加载三方库

  • jsPDF 内部依赖 html2canvas
  • 百度网站对第三方库的加载做了限制, 这个 demo 不能在百度里运行
// 控制台执行第一段
var jspdfScript = document.createElement('script') 
jspdfScript.src="https://unpkg.com/jspdf@latest/dist/jspdf.umd.min.js"; 
document.body.append(jspdfScript)
var html2canvasScript = document.createElement('script')
html2canvasScript.src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"; 
document.body.append(html2canvasScript)

第二步: 调试控制台 - element 页签, 选中某一个元素, demo 直接选取 <body>

第三步: 调试控制台 - 运行如下代码

    // 等 js 加载完成, 控制台执行第二段
    // $0 是调试工具选中的元素, 没有使用过的话, 可以直接控制台打印看看 $0 是啥
    window.jsPDF = jspdf.jsPDF
    html2canvas($0).then(function(canvas) {
        var max = { height:300 - 40 * 2, width: 210 - 15 * 2 }
        var doc = new jsPDF("p", "mm", 'a2')
        var height=canvas.height
        var width=canvas.width
        var ratio=canvas.height / canvas.width;
        if(height > max.height){// 先调整高
            height = max.height;
            width = height * ( 1 / ratio);
        }
        if (width > max.width) {// 再调整宽
            width = max.width
            height = width * ratio
        }
        // 最后宽高都是合适的
        doc.addImage(canvas, 'PNG', 15, 40, width, height)
        doc.save('sample-document.pdf')
    });

DOM 转 PDF 的 5 种方案

效果明显比 demo 4 要好, 并且没有乱码的问题,但这是一整张图片, 丢失了 pdf 内容可搜索, 可复制的特性;

我们的场景分析

将自定义长列表导出, 不需要操作按钮, 表格所有选项默认展开, 去掉装饰性元素.

方案概要css 媒体查询iframe 打开 DOM 元素将 body 重置为目标 dom 元素, 打印后还原 body三方库 jsPDF三方库 jsPDF + 三方库 html2canvas
结论不采纳作为华为云官网微服务, 大量框架相关的元素和外部样式采纳需要重新编写一套模板和 css, 成本略略高, 但是完全可控;备选不需要重新编写全套样式, 在 angular 样式模块的背景下也不会丢失全局的样式; 会丢失所有 DOM 元素的监听事件...要全局重新渲染一遍;不采纳字体库 20M, 前端加载用户体验非常差不采纳丢失了 pdf 文本属性, 不可搜索, 不可复制粘贴, 不满足业务要求