数字化办公时代,PDF以其跨平台、易于打印和分享等优点,成为了文档格式的首选。而今天,我们要学习一项超实用的前端黑科技,无需任何插件,就能将网页内容转化为PDF并下载,轻松提升工作效率!
npm install jspdf html2canvas
<button @click="exportPDF">导出PDF</button>
<div id="contentToConvert">
<!-- 需要转换为 PDF 的内容 -->
</div>
import jsPDF from 'jspdf';
import html2canvas from 'html2canvas';
export default {
methods: {
async exportPDF() {
const element = document.getElementById('contentToConvert');
const canvas = await html2canvas(element);
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({
orientation: 'p', // 纵向
unit: 'mm', // 单位
format: 'a4' // 纸张大小
});
const imgWidth = 210; // A4纸宽度,单位mm
const pageHeight = 297; // A4纸高度,单位mm
let imgHeight = (canvas.height * imgWidth) / canvas.width;
let position = 0;
let pageData = '';
// 处理多页情况
if (imgHeight < pageHeight) {
pageData = imgData;
} else {
while (imgHeight > 0) {
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight > pageHeight ? pageHeight : imgHeight);
imgHeight -= pageHeight;
position -= pageHeight;
if (imgHeight > 0) {
pdf.addPage();
}
}
}
// 添加图片到PDF
pdf.addImage(pageData, 'PNG', 0, 0, imgWidth, imgHeight>pageHeight?pageHeight:imgHeight);
// 保存PDF
pdf.save('download.pdf');
}
}
};
import jsPDF from 'jspdf';
import html2canvas from 'html2canvas';
这两行代码引入了 jsPDF 和 html2canvas 库,用于生成PDF和将HTML元素转换为 canvas。
const element = document.getElementById('contentToConvert');
这行代码获取了ID为 contentToConvert 的HTML元素,也就是我们需要转换为PDF的内容。
const canvas = await html2canvas(element);
这行代码使用 html2canvas 将目标元素转换为 canvas 对象。
const pdf = new jsPDF({
orientation: 'p', // 纵向
unit: 'mm', // 单位
format: 'a4' // 纸张大小
});
这行代码创建了一个新的 jsPDF 对象,并设置了PDF的页面方向、单位和纸张大小。
const imgWidth = 210; // A4纸宽度,单位mm
const pageHeight = 297; // A4纸高度,单位mm
let imgHeight = (canvas.height * imgWidth) / canvas.width;
let position = 0;
let pageData = '';
这几行代码计算了要添加到PDF中的图片的宽度、高度和位置。
pdf.addImage(pageData, 'PNG', 0, 0, imgWidth, imgHeight>pageHeight?pageHeight:imgHeight);
这行代码将图片添加到PDF文档中。
pdf.save('download.pdf');
这行代码将生成的PDF文件保存到本地,文件名为 download.pdf。
通过以上步骤,我们就能轻松地将网页内容转换为PDF并下载。这项技术不仅可以用于生成各种报表和文档,还可以用于制作电子书、电子票据等,应用场景非常广泛,快来试试吧!
近碰到个需求,需要把当前页面生成 pdf,并下载。弄了几天,自己整理整理,记录下来,我觉得应该会有人需要 :)
项目源码地址:https://github.com/linwalker/render-html-to-pdf
我们可以直接在浏览器端使用html2canvas,对整个或局部页面进行“截图”。但这并不是真的截图,而是通过遍历页面DOM结构,收集所有元素信息及相应样式,渲染出canvas image。
由于html2canvas只能将它能处理的生成canvas image,因此渲染出来的结果并不是100%与原来一致。但它不需要服务器参与,整个图片都由客户端浏览器生成,使用很方便。
使用
使用的API也很简洁,下面代码可以将某个元素渲染成canvas:
html2canvas(element, { onrendered: function(canvas) { // canvas is the final rendered <canvas> element } });
通过onrendered方法,可以将生成的canvas进行回调,比如插入到页面中:
html2canvas(element, { onrendered: function(canvas) { document.body.appendChild(canvas); } });
做个小例子(demo1)代码如下:
这个例子将页面body中的元素渲染成canvas,并插入到body中。
jsPDF库可以用于浏览器端生成PDF。
使用方法如下:
// 默认a4大小,竖直方向,mm单位的PDF var doc = new jsPDF(); // 添加文本‘Download PDF’ doc.text('Download PDF!', 10, 10); doc.save('a4.pdf');
// 三个参数,第一个方向,第二个尺寸,第三个尺寸格式 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')
生成pdf需要把转化的元素添加到jsPDF实例中,也有添加html的功能,但某些元素无法生成在pdf中,因此可以使用html2canvas + jsPDF的方式将页面转成pdf。通过html2canvas将遍历页面元素,并渲染生成canvas,然后将canvas图片格式添加到jsPDF实例,生成pdf。
单页
将demo1的例子修改下:
如果页面内容根据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是肿么来的,不用拉上去,直接看下面:
html2canvas(document.body, { onrendered:function(canvas) { //it is here we handle the canvas } })
这里的 body就是要生成canvas的元素对象,一个元素生成一个canvas;那么我们需要一页一页的canvas,也就是说。。。
你觉得可能吗? 我觉得不太现实,按这思路要获取页面上不同位置的DOM元素,然后通过 htnl2canvas(element,option)来处理,先不说能不能刚好在每个 pageHeight的位置刚好找到一个DOM元素,就算找到了,这样做累不累。
累的话 :)可以看看下面这种方法。
我提供的思路是我们只生成一个canvas,对就一个,转化元素就是你要转成pdf内容的母元素,在这篇demo里就是 body了;其他不变,也是超过一页内容就 addPage,然后 addImage,只不过这里添加的是同一个canvas。
当然这样做只会出现多页重复的pdf,那到底怎么实现正确分页显示。其实主要利用了jsPDF的两点:
虽然每一页pdf上显示的图片是相同的,但我们通过调整图片的位置,产生了分页的错觉。以第二页为例,将竖直方向上的偏移设置为 -841.89即一张a4纸的高度,又因为超过a4纸高度范围的图片不显示,所以第二页显示了图片竖直方向上[841.89,1682.78]范围内的内容,这就得到了分页的效果,以此类推。
还是看代码吧:
修改imgWidth,并且在addImage时x方向参数设置你要的边距,具体代码如下:
作者:linwalkerhttps://segmentfault.com/a/1190000009211079
路: 通过 html2canvas 将 HTML 页面转换成图片,然后再通过 jspdf 将图片的 base64 生成为 pdf 文件。
// 将页面 html 转换成图片
npm install html2canvas --save
// 将图片生成 pdf
npm install jspdf --save
复制代码
在项目主文件 main.js 中引入定义好的实现方法并注册
import htmlToPdf from '@/utils/htmlToPdf';
// 使用 Vue.use() 方法就会调用工具方法中的install方法
Vue.use(htmlToPdf);
复制代码
let pdf = new jsPDF('p', 'pt', [pdfX, pdfY]);
第一个参数: l:横向 p:纵向
第二个参数:测量单位("pt","mm", "cm", "m", "in" or "px");
第三个参数:可以是下面格式,默认为“a4”。如需自定义格式,只需将大小作为数字数组传递,如:[592.28, 841.89];
a0 - a10
b0 - b10
c0 - c10
dl
letter
government-letter
legal
junior-legal
ledger
tabloid
credit-card
复制代码
pdf.addPage() 在PDF文档中添加新页面,默认a4。参数如下:
pdf.addImage() 将图像添加到PDF。参数如下:
删除某页 pdf
let targetPage = pdf.internal.getNumberOfPages(); //获取总页
pdf.deletePage(targetPage); // 删除目标页
复制代码
保存 pdf 文档
pdf.save(`测试.pdf`);
复制代码
// 导出页面为PDF格式
import html2Canvas from 'html2canvas'
import JsPDF from 'jspdf'
export default{
install (Vue, options) {
Vue.prototype.getPdf = function () {
// 当下载pdf时,若不在页面顶部会造成PDF样式不对,所以先回到页面顶部再下载
let top = document.getElementById('pdfDom');
if (top != null) {
top.scrollIntoView();
top = null;
}
let title = this.exportPDFtitle;
html2Canvas(document.querySelector('#pdfDom'), {
allowTaint: true
}).then(function (canvas) {
// 获取canvas画布的宽高
let contentWidth = canvas.width;
let contentHeight = canvas.height;
// 一页pdf显示html页面生成的canvas高度;
let pageHeight = contentWidth / 841.89 * 592.28;
// 未生成pdf的html页面高度
let leftHeight = contentHeight;
// 页面偏移
let position = 0;
// html页面生成的canvas在pdf中图片的宽高(本例为:横向a4纸[841.89,592.28],纵向需调换尺寸)
let imgWidth = 841.89;
let imgHeight = 841.89 / contentWidth * contentHeight;
let pageData = canvas.toDataURL('image/jpeg', 1.0);
let PDF = new JsPDF('l', 'pt', 'a4');
// 两个高度需要区分: 一个是html页面的实际高度,和生成pdf的页面高度
// 当内容未超过pdf一页显示的范围,无需分页
if (leftHeight < pageHeight) {
PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
} else {
while (leftHeight > 0) {
PDF.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight)
leftHeight -= pageHeight;
position -= 592.28;
// 避免添加空白页
if (leftHeight > 0) {
PDF.addPage();
}
}
}
PDF.save(title + '.pdf')
})
}
}
}
复制代码
<template>
<div class="wrap" >
<div id="pdfDom" style="padding: 10px;">
<el-table
:data="tableData"
border>
<el-table-column prop="date" label="日期" width="250"></el-table-column>
<el-table-column prop="name" label="姓名" width="250"></el-table-column>
<el-table-column prop="address" label="地址"></el-table-column>
</el-table>
</div>
<button type="button" style="margin-top: 20px;" @click="btnClick">导出PDF</button>
</div>
</template>
<script>
export default {
data() {
return {
exportPDFtitle: "页面导出PDF文件名",
tableData: [
{
date: '2016-05-06',
name: '王小虎',
address: '重庆市九龙坡区火炬大道'
}, {
date: '2016-05-07',
name: '王小虎',
address: '重庆市九龙坡区火炬大道'
},{
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-08',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-06',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-06',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-07',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-08',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-06',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-07',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-06',
name: '王小虎',
address: '南京市江宁区将军大道'
}, {
date: '2016-05-07',
name: '王小虎',
address: '南京市江宁区将军大道'
},, {
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-08',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-06',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-07',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
},{
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-08',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-06',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-08',
name: '王小虎',
address: '武汉市洪山区文化大道'
}, {
date: '2016-05-06',
name: '王小虎',
address: '武汉市洪山区文化大道'
}, {
date: '2016-05-07',
name: '王小虎',
address: '武汉市洪山区文化大道'
}, {
date: '2016-05-06',
name: '王小虎',
address: '南京市江宁区将军大道'
}, {
date: '2016-05-07',
name: '王小虎',
address: '武汉市洪山区文化大道'
},
]
}
},
methods: {
btnClick(){
this.$nextTick(() => {this.getPdf();})
},
},
}
</script>
复制代码
如果你觉得这篇文章对你有点用的话,麻烦请给我们的开源项目点点star:http://github.crmeb.net/u/defu 不胜感激 !
*请认真填写需求信息,我们会在24小时内与您取得联系。