在Web应用开发中,PDF导出功能已经成为企业级应用的标配需求。从电商平台的订单导出,到管理系统的报表生成,再到教育平台的证书制作,PDF因其格式固定、易于打印和分享的特性,在各种业务场景中扮演着重要角色。
我曾在金融行业项目中,遇到过客户要求将动态生成的复杂数据报表导出为PDF的需求。最初尝试用服务端渲染方案,但面临响应延迟和服务器压力问题。后来转向纯前端方案后,不仅性能提升明显,用户体验也大幅改善。这正是JSPDF和HTML2Canvas这对黄金组合的价值所在。
JSPDF是一个轻量级的PDF生成库(仅约60KB),它提供了:
而HTML2Canvas(约25KB)则专注于:
这两个库配合使用时,可以发挥1+1>2的效果:
重要提示:虽然纯前端方案有很多优势,但对于超长内容(超过20页)或需要严格打印控制的场景,建议还是考虑服务端方案。
推荐使用npm/yarn安装最新稳定版:
bash复制npm install jspdf html2canvas --save
# 或
yarn add jspdf html2canvas
在项目中引入:
javascript复制import jsPDF from 'jspdf';
import html2canvas from 'html2canvas';
HTML2Canvas配置建议:
javascript复制{
scale: 2, // 提升输出质量
useCORS: true, // 处理跨域图片
allowTaint: true, // 允许污染画布
logging: false, // 生产环境关闭日志
backgroundColor: '#ffffff' // 设置白色背景
}
JSPDF初始配置:
javascript复制const doc = new jsPDF({
orientation: 'portrait', // 或 'landscape'
unit: 'mm',
format: 'a4'
});
javascript复制async function exportFullPage() {
// 获取整个body元素
const element = document.body;
// 转换为canvas
const canvas = await html2canvas(element, {
scale: 2
});
// 计算PDF尺寸
const imgData = canvas.toDataURL('image/jpeg', 1.0);
const pdfWidth = doc.internal.pageSize.getWidth();
const pdfHeight = (canvas.height * pdfWidth) / canvas.width;
// 添加到PDF
doc.addImage(imgData, 'JPEG', 0, 0, pdfWidth, pdfHeight);
// 保存文件
doc.save('export.pdf');
}
javascript复制async function exportElementById(elementId) {
const element = document.getElementById(elementId);
const canvas = await html2canvas(element);
const imgData = canvas.toDataURL('image/png');
const pdfWidth = doc.internal.pageSize.getWidth() - 20; // 留边距
const pdfHeight = (canvas.height * pdfWidth) / canvas.width;
doc.addImage(imgData, 'PNG', 10, 10, pdfWidth, pdfHeight);
doc.save('section_export.pdf');
}
javascript复制// 在PDF中嵌入字体
doc.addFont('NotoSansSC-Regular.ttf', 'NotoSansSC', 'normal');
doc.setFont('NotoSansSC');
css复制/* 为导出元素添加白色背景 */
.export-area {
background-color: white !important;
}
javascript复制// 在html2canvas配置中启用边框渲染
{
ignoreElements: (element) => false // 强制渲染所有元素
}
html复制<meta name="viewport" content="width=device-width, initial-scale=1.0">
javascript复制const isMobile = window.innerWidth < 768;
const scale = isMobile ? 1 : 2;
对于复杂页面,建议:
javascript复制async function renderInChunks(elements) {
const canvases = [];
for (const el of elements) {
canvases.push(await html2canvas(el));
}
// 合并处理...
}
javascript复制// 在worker中执行html2canvas转换
const worker = new Worker('pdf-worker.js');
javascript复制canvas = null;
imgData = null;
javascript复制// 当内容高度超过页面高度时自动分页
if (currentY > maxHeight) {
doc.addPage();
currentY = startY;
}
javascript复制const orderData = {
orderNo: '20230815001',
items: [
{ name: '商品A', price: 99, quantity: 2 },
{ name: '商品B', price: 199, quantity: 1 }
],
total: 397
};
javascript复制function generateOrderPDF(data) {
const doc = new jsPDF();
// 添加标题
doc.setFontSize(20);
doc.text(`订单号: ${data.orderNo}`, 15, 20);
// 添加表格
let y = 40;
doc.setFontSize(12);
data.items.forEach(item => {
doc.text(`${item.name}`, 15, y);
doc.text(`¥${item.price} x ${item.quantity}`, 150, y);
y += 10;
});
// 总计
doc.setFontSize(14);
doc.text(`总计: ¥${data.total}`, 15, y + 20);
doc.save(`order_${data.orderNo}.pdf`);
}
可能原因及解决方案:
跨域问题:
useCORS: trueHTTPS混合内容:
优化方案:
javascript复制{
scale: 3 // 根据需求调整
}
javascript复制doc.text('清晰文字', x, y);
javascript复制canvas.toDataURL('image/jpeg', 0.95); // 高质量JPEG
javascript复制function addHeaderFooter(doc) {
const pageCount = doc.internal.getNumberOfPages();
for (let i = 1; i <= pageCount; i++) {
doc.setPage(i);
// 页眉
doc.setFontSize(10);
doc.text('公司机密', 15, 10);
// 页脚
const footerY = doc.internal.pageSize.height - 10;
doc.text(`第 ${i} 页 / 共 ${pageCount} 页`, 105, footerY, { align: 'center' });
}
}
javascript复制// 在每页添加动态水印
doc.setGState(new doc.GState({ opacity: 0.3 }));
doc.setFontSize(40);
doc.setTextColor(200, 200, 200);
doc.text('草稿', 50, 150, { angle: 45 });
doc.setGState(new doc.GState({ opacity: 1 }));
javascript复制function checkPermission() {
return user.role === 'admin';
}
async function exportPDF() {
if (!checkPermission()) {
alert('无权限执行此操作');
return;
}
// 正常导出逻辑...
}
javascript复制const doc = new jsPDF();
// 设置密码保护
doc.setEncryption({
userPassword: 'user123',
ownerPassword: 'owner123',
userPermissions: ['print', 'modify']
});
在实际项目中,我发现合理设置html2canvas的scale参数对输出质量影响最大。对于普通文档,scale=2足够清晰;对于高精度设计稿,可能需要提高到3-4。但要注意,每增加1倍的scale,内存占用会呈平方级增长,在移动设备上要特别小心内存溢出问题。