精品推荐:Javascript 将 HTML 页面生成 PDF 并下载

软件老王 2018-07-16 13:53:03 ⋅ 823 阅读

作者:linwalker

https://segmentfault.com/a/1190000009211079


最近碰到个需求,需要把当前页面生成 pdf,并下载。弄了几天,自己整理整理,记录下来,我觉得应该会有人需要 :)

项目源码地址:https://github.com/linwalker/render-html-to-pdf

html2canvas

简介

我们可以直接在浏览器端使用html2canvas,对整个或局部页面进行“截图”。但这并不是真的截图,而是通过遍历页面DOM结构,收集所有元素信息及相应样式,渲染出canvas image。

由于html2canvas只能将它能处理的生成canvas image,因此渲染出来的结果并不是100%与原来一致。但它不需要服务器参与,整个图片都由客户端浏览器生成,使用很方便。

使用

使用的API也很简洁,下面代码可以将某个元素渲染成canvas:

 
  1. html2canvas(element, {

  2.    onrendered: function(canvas) {

  3.        // canvas is the final rendered <canvas> element

  4.    }

  5. });

通过onrendered方法,可以将生成的canvas进行回调,比如插入到页面中:

 
  1. html2canvas(element, {

  2.    onrendered: function(canvas) {

  3.       document.body.appendChild(canvas);

  4.    }

  5. });

做个小例子(demo1)代码如下:

 
  1. <html>

  2.  <head>

  3.    <title>html2canvas example</title>

  4.    <style type="text/css">...</style>

  5.  </head>

  6.  <body>

  7.    <header>

  8.      <nav>

  9.        <ul>

  10.          <li>one</li>

  11.          ...

  12.        </ul>

  13.      </nav>

  14.    </header>

  15.    <section>

  16.      <aside>

  17.        <h3>it is a title</h3>

  18.        <a href="">Stone Giant</a>

  19.        ...

  20.     </aside>

  21.      <article>

  22.        <img src="./Stone.png">

  23.        <h2>Stone Giant</h2>

  24.        <p>Coming ... </p>

  25.        <p>以一团石头...</p>

  26.      </article>

  27.    </section>

  28.    <footer>write by linwalker @2017</footer>

  29.    <script type="text/javascript" src="./html2canvas.js"></script>

  30.    <script type="text/javascript">

  31.        html2canvas(document.body, {

  32.          onrendered:function(canvas) {

  33.            document.body.appendChild(canvas)

  34.          }

  35.        })

  36.    </script>

  37.  </body>

  38. </html>

这个例子将页面body中的元素渲染成canvas,并插入到body中。

jsPDF

jsPDF库可以用于浏览器端生成PDF。

文字生成PDF

使用方法如下:

 
  1. // 默认a4大小,竖直方向,mm单位的PDF

  2. var doc = new jsPDF();

  3. // 添加文本‘Download PDF’

  4. doc.text('Download PDF!', 10, 10);

  5. doc.save('a4.pdf');

图片生成PDF

使用方法如下:

 
  1. // 三个参数,第一个方向,第二个单位,第三个尺寸格式

  2. var doc = new jsPDF('landscape','pt',[205, 115])

  3. // 将图片转化为dataUrl

  4. var imageData = ‘data:image/png;base64,iVBORw0KGgo...’;

  5. doc.addImage(imageData, 'PNG', 0, 0, 205, 115);

  6. doc.save('a4.pdf');

文字与图片生成PDF
 
  1. // 三个参数,第一个方向,第二个尺寸,第三个尺寸格式

  2. var doc = new jsPDF('landscape','pt',[205, 155])

  3. // 将图片转化为dataUrl

  4. var imageData = ‘data:image/png;base64,iVBORw0KGgo...’;

  5. //设置字体大小

  6. doc.setFontSize(20);

  7. //10,20这两参数控制文字距离左边,与上边的距离

  8. doc.text('Stone', 10, 20);

  9. // 0, 40, 控制文字距离左边,与上边的距离

  10. doc.addImage(imageData, 'PNG', 0, 40, 205, 115);

  11. doc.save('a4.pdf')

生成pdf需要把转化的元素添加到jsPDF实例中,也有添加html的功能,但某些元素无法生成在pdf中,因此可以使用html2canvas + jsPDF的方式将页面转成pdf。通过html2canvas将遍历页面元素,并渲染生成canvas,然后将canvas图片格式添加到jsPDF实例,生成pdf。

html2canvas + jsPDF

单页

将demo1的例子修改下:

 
  1. <script type="text/javascript" src="./js/jsPdf.debug.js"></script>

  2. <script type="text/javascript">

  3.      var downPdf = document.getElementById("renderPdf");

  4.      downPdf.onclick = function() {

  5.          html2canvas(document.body, {

  6.              onrendered:function(canvas) {

  7.                  //返回图片dataURL,参数:图片格式和清晰度(0-1)

  8.                  var pageData = canvas.toDataURL('image/jpeg', 1.0);

  9.                  //方向默认竖直,尺寸ponits,格式a4[595.28,841.89]

  10.                  var pdf = new jsPDF('', 'pt', 'a4');

  11.                  //addImage后两个参数控制添加图片的尺寸,此处将页面高度按照a4纸宽高比列进行压缩

  12.                  pdf.addImage(pageData, 'JPEG', 0, 0, 595.28, 592.28/canvas.width * canvas.height );

  13.                  pdf.save('stone.pdf');

  14.              }

  15.          })

  16.      }

  17. </script>

如果页面内容根据a4比例转化后高度超过a4纸高度呢,生成的pdf会怎么样?会分页吗?

你可以试试,验证一下自己的想法。

jsPDF提供了一个很有用的API, addPage(),我们可以通过 pdf.addPage(),来添加一页pdf,然后通过 pdf.addImage(...),将图片赋予这页pdf来显示。

那么我们如何确定哪里分页?

这个问题好回答,我们可以设置一个 pageHeight,超过这个高度的内容放入下一页pdf。

来捋一下思路,将html页面内容生成canvas图片,通过 addImage将第一页图片添加到pdf中,超过一页内容,通过 addPage()添加pdf页数,然后再通过 addImage将下一页图片添加到pdf中。

嗯~,很好!巴特,难道没有发现问题吗?

这个方法实现的前提是 — — 我们能根据 pageHeight先将整页内容生成的canvas图片分割成对应的小图片,然后一个萝卜一个坑,一页一页 addImage进去。

What? 想一想我们的canvas是肿么来的,不用拉上去,直接看下面:

 
  1. html2canvas(document.body, {

  2.    onrendered:function(canvas) {

  3.     //it is here we handle the canvas

  4.    }

  5. })

这里的 body就是要生成canvas的元素对象,一个元素生成一个canvas;那么我们需要一页一页的canvas,也就是说。。。

你觉得可能吗? 我觉得不太现实,按这思路要获取页面上不同位置的DOM元素,然后通过 htnl2canvas(element,option)来处理,先不说能不能刚好在每个 pageHeight的位置刚好找到一个DOM元素,就算找到了,这样做累不累。

累的话 :)可以看看下面这种方法。

多页

我提供的思路是我们只生成一个canvas,对就一个,转化元素就是你要转成pdf内容的母元素,在这篇demo里就是 body了;其他不变,也是超过一页内容就 addPage,然后 addImage,只不过这里添加的是同一个canvas。

当然这样做只会出现多页重复的pdf,那到底怎么实现正确分页显示。其实主要利用了jsPDF的两点:

  • 超过jsPDF实例格式尺寸的内容不显示( varpdf=newjsPDF('','pt','a4');demo中就是a4纸的尺寸)

  • addImage有两个参数可以控制图片在pdf中的位置

虽然每一页pdf上显示的图片是相同的,但我们通过调整图片的位置,产生了分页的错觉。以第二页为例,将竖直方向上的偏移设置为 -841.89即一张a4纸的高度,又因为超过a4纸高度范围的图片不显示,所以第二页显示了图片竖直方向上[841.89,1682.78]范围内的内容,这就得到了分页的效果,以此类推。

还是看代码吧:

 
  1. html2canvas(document.body, {

  2.  onrendered:function(canvas) {

  3.      var contentWidth = canvas.width;

  4.      var contentHeight = canvas.height;

  5.      //一页pdf显示html页面生成的canvas高度;

  6.      var pageHeight = contentWidth / 592.28 * 841.89;

  7.      //未生成pdf的html页面高度

  8.      var leftHeight = contentHeight;

  9.      //页面偏移

  10.      var position = 0;

  11.      //a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高

  12.      var imgWidth = 595.28;

  13.      var imgHeight = 592.28/contentWidth * contentHeight;

  14.      var pageData = canvas.toDataURL('image/jpeg', 1.0);

  15.      var pdf = new jsPDF('', 'pt', 'a4');

  16.      //有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)

  17.      //当内容未超过pdf一页显示的范围,无需分页

  18.      if (leftHeight < pageHeight) {

  19.      pdf.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight );

  20.      } else {

  21.          while(leftHeight > 0) {

  22.              pdf.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight)

  23.              leftHeight -= pageHeight;

  24.              position -= 841.89;

  25.              //避免添加空白页

  26.              if(leftHeight > 0) {

  27.                pdf.addPage();

  28.              }

  29.          }

  30.      }

  31.      pdf.save('content.pdf');

  32.  }

  33. })

两边留边距

修改imgWidth,并且在addImage时x方向参数设置你要的边距,具体代码如下:

 
  1. var imgWidth = 555.28;

  2. var imgHeight = 555.28/contentWidth * contentHeight;

  3. ...

  4. pdf.addImage(pageData, 'JPEG', 20, 0, imgWidth, imgHeight );

  5. ...

  6. pdf.addImage(pageData, 'JPEG', 20, position, imgWidth, imgHeight);


文章来源

原文作者:linwalker

公众号:  编程微刊

更多精彩内容请关注“IT实战联盟”哦~~~



全部评论: 0

    我有话说:

    FreeMarker使用模板生成HTML静态页面

    哈喽,最近的项目中正好在做一个发布新闻的功能,每个新闻可能要有不同的防蚊页面喽~

    Poppler 21.01.0 发布,PDF 生成工具

    Poppler 21.01.0 发布了。 Poppler 是从 Xpdf 3.0 代码库派生而来的 PDF 生成工具包。Poppler 使用了各种 PDF 类库(Evince,Okular

    精品推荐:11个高人气Javascript动画库

    翻译 | 小生 来源 | https://www.zcfy.cc/article/11-javascript-animation-libraries-for-2018

    精品推荐:大神总结的十大 JavaScript 错误及如何规避

    通过统计数据库中的1000多个项目,我们发现在 JavaScript 中最常出现的错误有10个。下面会向大家介绍这些错误发生的原因以及如何防止。

    微信小程序微商城(九):微信授权实现个人中心页面页面

    实现微商城的微信授权获取用户信息和个人中心页面布局

    下载」Spring Cloud Alibaba 从入门到实战.pdf

    前言 近些年随着云技术的发展,越来越多的用户选择使用云技术来代替传统的 IT 基础设 施。在云技术发展的早期,业界的关注点集中在虚拟化、分布式、存储等 Iaas 方面的技 术。但是随着“云原生

    Scala.js 1.4.0 发布, Scala 编译成 JavaScript

    Scala.js 1.4.0 已发布。Scala.js 是一个 Scala 语言编译成 JavaScript 的工具,以便可以直接在浏览器上使用 Scala 编写程序。 Scala.js

    精品推荐:Redis主从复制

    持久化保证了即使 redis 服务重启也会丢失数据,因为 redis 服务重启后会硬盘上持久化的数据恢复到内存中,但是当 redis 服务器的硬盘损坏了可能会导致数据丢失,如果通过 redis 的

    「开源资讯」React 17 正式版发布,构建用户界面的 JavaScript

    React简介 React(有时叫React.js或ReactJS)是 Facebook 推出的一个为数据提供渲染为 HTML 视图,用来构建用户界面的开源 JavaScript 库。 React

    有趣的404页面设计鉴赏

    经过设计后,这个提示页面会更友好些,下面来欣赏一波404页面的设计。

    CKEditor 5 v23.1.0 发布,支持嵌入 Raw HTML

    CKEditor 5 v23.1.0 稳定版已发布,主要更新内容包括:支持在编辑器嵌入原生 HTML 代码进行渲染、改进 reconversion API 以及支持表格内容粘贴到另一个表格

    精品推荐:4个顶级开源JavaScript图表库

    图表对于可视化数据和使网站具有吸引力非常重要。可视化演示使分析大块数据和传达信息变得更加容易。

    「开源推荐」Nginx可视化配置工具—NginxWebUI,小白也可以玩转

    包括http协议转发, tcp协议转发, 反向代理, 负载均衡, ssl证书自动申请、续签、配置等

    精品推荐:Redis 5 新特性之Stream

    Stream 是一个日志形式的存储结构,可以往里追加数据,每条数据都会生成一个时间戳ID,Stream 也有便捷的读取数据的模型.

    HTML实现某宝优惠券、商品列表和活动悬浮等布局(文末有源码)

    最近温习一下HTML5+CSS3的一些特性,准备找个高仿的目标,最后选择了某宝粉丝福利页面