DOM 转 PDF 的 5 种方案

项目中想要将侧边弹窗里的内容导出成 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 另存为 pdf

1. 将整个 body 的 dom 节点缓存下来

2. 将 body 设置为打印的目标元素

3. 触发 window.print 另存为 pdf

4. 还原 body 的 dom 节点

1. 可以创建 pdf 文件, 然后逐个元素添加至 pdf 文件中;

2. 也可以将 dom 节点转换为 pdf

1. html2canvas 将 dom 节点转换为图片

2. jsPDF 将图片转换为 pdf 文件

优点 – 常规打印样式设置, 可以用于网页 pdf 导出; 也可以稍加调整用于网页局部 pdf 导出;

– 改动难度小, 不需要编写 JS 逻辑

– 可以将任意内容导出成 pdf 文档, 而不必是网页上的内容 – 改动非常小, 实现非常快速 – 可以完全掌控 pdf 文件的构造, 逐个段落输出 – 改动非常小, 实现非常快速
缺点 – 没有明显缺点 – 没有明显缺点 – 会丢失 DOM 节点绑点的事件, 大部分场景这思路就不可用

– 打印过程中, 网页内容被改变, 略微影响用户体验

– 目标元素的样式如果和 DOM 层级结构相关将会丢失

– 需要前端引入字体库, 否则就是乱码 (20M 左右),

– 直接将 dom 节点转 pdf 效果较差, 丢失大量样式设置

– 丢失 pdf 文档的文本内容, 和直接导出图片没有区别

– html2canvas 库的 bug, 会造成部分样式错误渲染, 比如阴影;

demo demo 1 demo 2 demo 3 demo 4 demo 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>

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

image.png

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

image.png

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)

image.png

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

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

image.png

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

demo 4 三方库 jsPDF

示例网站: google.com

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

  • jsPDF 内部依赖 html2canvas
  • 百度网站对第三方库的加载做了限制, 这个 demo 不能在百度里运行 (有一些网站做了内容安全策略, 如 github 等, 所以这个 demo 的运行还挑网站 — juejin 可以运行这个 demo)
  • 由于 jspdf 方案必须依赖字体库, 本 demo 不考虑解决这个问题, 所以挑选了一个几乎全英文的网站来进行演示
  • jspdf 方案, 必然要面对表格样式变形, 图片变形等问题, 比较难解决;
    // 控制台执行第一段
    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
    });

image.png

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

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')
    });

image.png

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

我们的场景分析

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

方案概要 css 媒体查询 iframe 打开 DOM 元素 将 body 重置为目标 dom 元素, 打印后还原 body 三方库 jsPDF 三方库 jsPDF + 三方库 html2canvas
结论 不采纳

作为华为云官网微服务, 大量框架相关的元素和外部样式

采纳

需要重新编写一套模板和 css, 成本略略高, 但是完全可控;

备选

不需要重新编写全套样式, 在 angular 样式模块的背景下也不会丢失全局的样式; 会丢失所有 DOM 元素的监听事件…要全局重新渲染一遍;

不采纳

字体库 20M, 前端加载用户体验非常差

不采纳

丢失了 pdf 文本属性, 不可搜索, 不可复制粘贴, 不满足业务要求

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYDxD9GA' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片