README.md 9.5 KB
Newer Older
L
Lin 已提交
1
# html转成pdf,下载(html2canvas 和 jsPDF)
L
Lin 已提交
2 3

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


## html2canvas

L
linwalker 已提交
8
#### 简介
L
linwalker 已提交
9 10 11 12 13

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

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

L
linwalker 已提交
14
#### 使用
L
linwalker 已提交
15 16 17

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

L
readme  
linwalker 已提交
18
```javascript
L
linwalker 已提交
19 20 21 22 23 24 25
html2canvas(element, {
    onrendered: function(canvas) {
        // canvas is the final rendered <canvas> element
    }
});
```

L
Lin 已提交
26
通过onrendered方法,可以将生成的canvas进行回调,比如插入到页面中:
L
linwalker 已提交
27

L
readme  
linwalker 已提交
28
```javascript
L
linwalker 已提交
29 30 31 32 33 34 35
html2canvas(element, {
    onrendered: function(canvas) {
       document.body.appendChild(canvas);
    }
});
```

L
linwalker 已提交
36
做个小例子代码如下,在线展示链接[demo1](https://linwalker.github.io/render-html-to-pdf/demo1.html)
L
linwalker 已提交
37

L
fix  
linwalker 已提交
38
``` html
L
linwalker 已提交
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
<html>
  <head>
    <title>html2canvas example</title>
    <style type="text/css">...</style>
  </head>
  <body>
    <header>
      <nav>
        <ul>
          <li>one</li>
          ...
        </ul>
      </nav>
    </header>
    <section>
      <aside>
        <h3>it is a title</h3>
        <a href="">Stone Giant</a>
        ...
     </aside>
      <article>
        <img src="./Stone.png">
        <h2>Stone Giant</h2>
        <p>Coming ... </p>
        <p>以一团石头...</p>
      </article>
    </section>
    <footer>write by linwalker @2017</footer>
    <script type="text/javascript" src="./html2canvas.js"></script>
    <script type="text/javascript">
        html2canvas(document.body, {
          onrendered:function(canvas) {
            document.body.appendChild(canvas)
          }
        })
    </script>
  </body>
L
linwalker 已提交
76 77 78
</html>
```

L
Lin 已提交
79 80
这个例子将页面body中的元素渲染成canvas,并插入到body中

L
linwalker 已提交
81 82 83 84 85 86 87 88
## jsPDF

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

#### 文字生成PDF

使用方法如下:

L
readme  
linwalker 已提交
89
```javascript
L
linwalker 已提交
90 91 92 93 94 95 96 97 98 99 100 101 102 103
// 默认a4大小,竖直方向,mm单位的PDF
var doc = new jsPDF();

// 添加文本‘Download PDF’
doc.text('Download PDF!', 10, 10);
doc.save('a4.pdf');
```

在线演示[demo2](https://linwalker.github.io/render-html-to-pdf/demo2.html)

#### 图片生成PDF

使用方法如下:

L
readme  
linwalker 已提交
104
```javascript
L
Lin 已提交
105
// 三个参数,第一个方向,第二个单位,第三个尺寸格式
L
linwalker 已提交
106 107 108 109 110
var doc = new jsPDF('landscape','pt',[205, 115])

// 将图片转化为dataUrl
var imageData = data:image/png;base64,iVBORw0KGgo...;

L
linwalker 已提交
111
doc.addImage(imageData, 'PNG', 0, 0, 205, 115);
L
linwalker 已提交
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
doc.save('a4.pdf');
```

在线演示[demo3](https://linwalker.github.io/render-html-to-pdf/demo3.html)

#### 文字与图片生成PDF

```javascript
// 三个参数,第一个方向,第二个尺寸,第三个尺寸格式
var doc = new jsPDF('landscape','pt',[205, 155])

// 将图片转化为dataUrl
var imageData = data:image/png;base64,iVBORw0KGgo...;

//设置字体大小
doc.setFontSize(20);

//10,20这两参数控制文字距离左边,与上边的距离
doc.text('Stone', 10, 20);

// 0, 40, 控制文字距离左边,与上边的距离
doc.addImage(imageData, 'PNG', 0, 40, 205, 115);
doc.save('a4.pdf')
```

在线演示[demo4](https://linwalker.github.io/render-html-to-pdf/demo4.html)

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

## html2canvas + jsPDF

#### 单页

将demo1的例子修改下:

```javascript
<script type="text/javascript" src="./js/jsPdf.debug.js"></script>
<script type="text/javascript">
      var downPdf = document.getElementById("renderPdf");
      downPdf.onclick = function() {
          html2canvas(document.body, {
              onrendered:function(canvas) {

                  //返回图片dataURL,参数:图片格式和清晰度(0-1)
                  var pageData = canvas.toDataURL('image/jpeg', 1.0);

                  //方向默认竖直,尺寸ponits,格式a4[595.28,841.89]
                  var pdf = new jsPDF('', 'pt', 'a4');

L
linwalker 已提交
161
                  //addImage后两个参数控制添加图片的尺寸,此处将页面高度按照a4纸宽高比列进行压缩
L
linwalker 已提交
162 163 164 165 166 167 168 169 170 171 172
                  pdf.addImage(pageData, 'JPEG', 0, 0, 595.28, 592.28/canvas.width * canvas.height );

                  pdf.save('stone.pdf');

              }
          })
      }
</script>
```
在线演示[demo5](https://linwalker.github.io/render-html-to-pdf/demo5.html)

L
Lin 已提交
173
如果页面内容根据a4比例转化后高度超过a4纸高度呢,生成的pdf会怎么样?会分页吗?
L
linwalker 已提交
174 175 176 177 178 179 180 181 182 183 184

你可以试试,验证一下自己的想法: [demo6](https://linwalker.github.io/render-html-to-pdf/demo6.html)

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

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

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

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

L
Lin 已提交
185 186 187
嗯~,很好!巴特,难道没有发现问题吗?

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

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

L
readme  
linwalker 已提交
191 192 193 194 195 196
```javascript
html2canvas(document.body, {
    onrendered:function(canvas) {
     //it is here we handle the canvas
    }
})
L
linwalker 已提交
197 198 199 200 201 202 203 204 205
```

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

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

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

L
readme  
linwalker 已提交
206

L
readme  
linwalker 已提交
207
#### 多页
L
linwalker 已提交
208

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

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

L
linwalker 已提交
213 214 215
	- 超过jsPDF实例格式尺寸的内容不显示
	(var pdf = new jsPDF('', 'pt', 'a4'); demo中就是a4纸的尺寸)
	- addImage有两个参数可以控制图片在pdf中的位置
L
linwalker 已提交
216

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

还是看代码吧:

L
readme  
linwalker 已提交
221
```javascript
L
Lin 已提交
222 223 224 225 226 227
html2canvas(document.body, {
  onrendered:function(canvas) {

      var contentWidth = canvas.width;
      var contentHeight = canvas.height;

L
Lin 已提交
228
      //一页pdf显示html页面生成的canvas高度;
L
Lin 已提交
229
      var pageHeight = contentWidth / 592.28 * 841.89;
L
Lin 已提交
230
      //未生成pdf的html页面高度
L
Lin 已提交
231 232 233
      var leftHeight = contentHeight;
      //页面偏移
      var position = 0;
L
Lin 已提交
234
      //a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
L
Lin 已提交
235 236 237 238 239 240 241
      var imgWidth = 595.28;
      var imgHeight = 592.28/contentWidth * contentHeight;

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

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

L
Lin 已提交
242
      //有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
L
Lin 已提交
243 244 245 246
      //当内容未超过pdf一页显示的范围,无需分页
      if (leftHeight < pageHeight) {
	  pdf.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight );
      } else {
L
Lin 已提交
247 248 249 250 251 252 253 254
	      while(leftHeight > 0) {
	          pdf.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight)
	          leftHeight -= pageHeight;
	          position -= 841.89;
	          //避免添加空白页
	          if(leftHeight > 0) {
		        pdf.addPage();
	          }
L
linwalker 已提交
255
	      }
L
Lin 已提交
256 257 258 259 260
      }

      pdf.save('content.pdf');
  }
})
L
linwalker 已提交
261 262 263
```

在线演示[demo7](https://linwalker.github.io/render-html-to-pdf/demo7.html)
L
linwalker 已提交
264

L
gauge  
linwalker 已提交
265 266 267 268 269 270 271 272 273 274
#### 两边留边距
修改imgWidth,并且在addImage时x方向参数设置你要的边距,具体代码如下
```javascript
var imgWidth = 555.28;
var imgHeight = 555.28/contentWidth * contentHeight;
...
pdf.addImage(pageData, 'JPEG', 20, 0, imgWidth, imgHeight );
...
pdf.addImage(pageData, 'JPEG', 20, position, imgWidth, imgHeight);
```
L
demo8  
linwalker 已提交
275
在线演示[demo8](https://linwalker.github.io/render-html-to-pdf/demo8.html)
L
gauge  
linwalker 已提交
276

L
linwalker 已提交
277 278 279 280 281 282 283
最后附上几个jsPDF的官方网址:

<http://rawgit.com/MrRio/jsPDF/master/>

<http://rawgit.com/MrRio/jsPDF/master/docs/index.html>

<http://mrrio.github.io/>