旨在通过 html2canvas 和 jspdf,先将页面的 html 转成 canvas,再将 canvas 转成 pdf,同时解决了分页截断的问题。

安装依赖

yarn add html2canvas
yarn add jspdf

思路

通过网上的一些教程,初步实现了 html 转 pdf 的功能,将一整个 DOM 元素放进去,虽然可以粗糙实现,但是出现了很多地方被分页截断的情况,这个时候就需要在某一张图片被截断时,将其自动切换到下一页中。

1.拆解父节点

所以第一步:拆解父节点,一行一行细分为很多子节点,循环遍历这些子节点,累加这些子节点的高度,如果超出了 a4 纸(210*297)的高度,则分页。

import html2Canvas from "html2canvas";
import JsPDF from "jspdf"; export function oneNodeMultipleChildren(title, node) {
html2Canvas(node, {
scale: 2, // 清晰度
}).then(function (canvas) {
let PDF = new JsPDF("", "mm", "a4"); // 以mm为单位
let position = 0; // 页面偏移
let contentWidth = canvas.width; // 转换成canvas后的宽度
let contentHeight = canvas.height; // 转换成canvas后的高度
let proportion = 210 / node.offsetWidth; // html缩小至a4纸大小时的比例
let currentHeight = 0; // 当前高度
let imgWidth = 210; // canvas缩小至a4纸大小时的宽度
let imgHeight = (210 / contentWidth) * contentHeight; // canvas缩小至a4纸大小时的高度
let pageData = canvas.toDataURL("image/jpeg", 1.0); // 将canvas转成图片 for (let j = 0; j < node.children.length; j++) {
let childHeight = (node.children[j].offsetHeight + 8) * proportion; // 页面中每行的间距 margin-bottom: 8px if (currentHeight + childHeight > 297) {
// 如果加上这个子节点后内容超出a4纸高度,就从当前位置开始分页
addImage(PDF, pageData, position, imgWidth, imgHeight, currentHeight);
position -= currentHeight; // 这一页放了多少高度的内容,下一页就从这个高度开始偏移
if (position >= -contentHeight) {
PDF.addPage(); // 添加新pdf页
}
currentHeight = childHeight; // 下一页第一个元素的高度
} else {
currentHeight += childHeight;
}
}
addImage(PDF, pageData, position, imgWidth, imgHeight, currentHeight); // 最后一页
PDF.save(title + ".pdf");
});
} function addImage(PDF, pageData, position, imgWidth, imgHeight, currentHeight) {
PDF.addImage(pageData, "JPEG", 0, position, imgWidth, imgHeight); // 在当前pdf页添加图片
PDF.setFillColor(255, 255, 255); // 遮挡的颜色
PDF.rect(0, currentHeight, 210, Math.ceil(297 - currentHeight), "F"); // 添加空白遮挡
// PDF.rect参数分别为:起始横坐标、起始纵坐标、绘制宽度、绘制高度、填充色
}

2.合并父节点

经过上述步骤,一个父节点多个子节点,并且每个子节点独占一行的布局可以实现分页,那要是有很多父节点呢?就需要遍历每个父节点,合并所有子节点,进行分页截断。

import html2Canvas from "html2canvas";
import JsPDF from "jspdf"; export function exportPdf(title, id) {
let content = document.querySelector(`#${id}`);
let first = content.firstElementChild.firstElementChild;
let second = content.lastElementChild;
oneNodeMultipleChildren(title, content, [first, second]);
} export function oneNodeMultipleChildren(title, content, nodes) {
html2Canvas(content, {
scale: 2,
}).then(function (canvas) {
let PDF = new JsPDF("", "mm", "a4");
let position = 0;
let contentWidth = canvas.width;
let contentHeight = canvas.height;
let proportion = 200 / content.offsetWidth;
let currentHeight = 0;
let imgWidth = 200;
let imgHeight = (200 / contentWidth) * contentHeight;
let pageData = canvas.toDataURL("image/jpeg", 1.0); for (let i = 0; i < nodes.length; i++) {
// 根据传入的父节点数量进行循环,遍历父节点,合并所有子节点
for (let j = 0; j < nodes[i].children.length; j++) {
let childHeight = (nodes[i].children[j].offsetHeight + 8) * proportion; if (currentHeight + childHeight > 287) {
addImage(PDF, pageData, position, imgWidth, imgHeight, currentHeight);
position -= currentHeight;
if (position >= -contentHeight) {
PDF.addPage();
}
currentHeight = childHeight;
} else {
currentHeight += childHeight;
}
}
}
addImage(PDF, pageData, position, imgWidth, imgHeight, currentHeight);
PDF.save(title + ".pdf");
});
} function addImage(PDF, pageData, position, imgWidth, imgHeight, currentHeight) {
PDF.addImage(pageData, "JPEG", 0, position, imgWidth, imgHeight); // 在当前pdf页添加图片
PDF.setFillColor(255, 255, 255); // 遮挡的颜色
PDF.rect(0, currentHeight, 210, Math.ceil(297 - currentHeight), "F"); // 添加空白遮挡
}

3.每行多个元素

这个时候新的问题出现了,由于页面布局为 flex 布局,不同缩放下,每行的元素数量会出现变化。所以我们获取第一个子元素与 a4 纸宽度关系,如果为 n 倍,那后面 n-1 个子元素的高度不进行累加。

这里只解决了一行 n 个子元素宽度相等,且近似等于 a4 纸宽度的 1/n 的情况。

import html2Canvas from "html2canvas";
import JsPDF from "jspdf"; export function exportAssetPdf(title, id) {
let content = document.querySelector(`#${id}`);
let first = content.firstElementChild.firstElementChild;
let second = content.lastElementChild;
oneNodeMultipleChildren(title, content, [first, second]);
} export function oneNodeMultipleChildren(title, content, nodes) {
html2Canvas(content, {
scale: 2,
}).then(function (canvas) {
let PDF = new JsPDF("", "mm", "a4");
let position = 0;
let contentWidth = canvas.width;
let contentHeight = canvas.height;
let proportion = 200 / content.offsetWidth;
let currentHeight = 0;
let imgWidth = 200;
let imgHeight = (200 / contentWidth) * contentHeight;
let pageData = canvas.toDataURL("image/jpeg", 1.0);
let sameIndex = 1;
let widthX = 1; for (let i = 0; i < nodes.length; i++) {
for (let j = 0; j < nodes[i].children.length; j++) {
let childHeight = (nodes[i].children[j].offsetHeight + 8) * proportion;
let childWidth = nodes[i].children[j].offsetWidth * proportion;
if (sameIndex === 1) {
widthX = Math.round(200 / childWidth);
}
if (sameIndex < widthX) {
childHeight = 0;
sameIndex++;
} else {
sameIndex = 1;
} if (currentHeight + childHeight > 287) {
addImage(PDF, pageData, position, imgWidth, imgHeight, currentHeight);
position -= currentHeight;
if (position >= -contentHeight) {
PDF.addPage();
}
currentHeight = childHeight;
} else {
currentHeight += childHeight;
}
}
}
addImage(PDF, pageData, position, imgWidth, imgHeight, currentHeight);
PDF.save(title + ".pdf");
});
} function addImage(PDF, pageData, position, imgWidth, imgHeight, currentHeight) {
PDF.addImage(pageData, "JPEG", 0, position, imgWidth, imgHeight); // 在当前pdf页添加图片
PDF.setFillColor(255, 255, 255); // 遮挡的颜色
PDF.rect(0, currentHeight, 210, Math.ceil(297 - currentHeight), "F"); // 添加空白遮挡
}

4.添加左右间距和页眉页脚

为了美化 pdf 布局,上下左右留白,就需要添加左右间距和页眉页脚:减少 html 缩小至 a4 纸大小时的比例和 canvas 缩小至 a4 纸大小时宽高,增加偏移量,并对页眉页脚进行空白遮挡。

import html2Canvas from "html2canvas";
import JsPDF from "jspdf"; export function exportAssetPdf(title, id) {
let content = document.querySelector(`#${id}`);
let first = content.firstElementChild.firstElementChild;
let second = content.lastElementChild;
oneNodeMultipleChildren(title, content, [first, second]);
} export function oneNodeMultipleChildren(title, fNode, sNode) {
html2Canvas(fNode, {
scale: 2,
}).then(function (canvas) {
let PDF = new JsPDF("", "mm", "a4");
let position = 0;
let contentWidth = canvas.width;
let contentHeight = canvas.height;
let proportion = 200 / fNode.offsetWidth; // 减少10mm
let currentHeight = 0;
let imgWidth = 200; // 减少10mm
let imgHeight = (200 / contentWidth) * contentHeight; // 减少10mm
let pageData = canvas.toDataURL("image/jpeg", 1.0);
let sameIndex = 1;
let widthX = 1; for (let i = 0; i < sNode.length; i++) {
for (let j = 0; j < sNode[i].children.length; j++) {
let childHeight = (sNode[i].children[j].offsetHeight + 8) * proportion;
let childWidth = sNode[i].children[j].offsetWidth * proportion;
if (sameIndex === 1) {
widthX = Math.round(200 / childWidth); // 减少10mm
}
if (sameIndex < widthX) {
childHeight = 0;
sameIndex++;
} else {
sameIndex = 1;
} if (currentHeight + childHeight > 287) {
// 减小10mm
addImage(PDF, pageData, position, imgWidth, imgHeight, currentHeight);
position -= currentHeight;
if (position >= -contentHeight) {
PDF.addPage();
}
currentHeight = childHeight;
} else {
currentHeight += childHeight;
}
}
}
addImage(PDF, pageData, position, imgWidth, imgHeight, currentHeight);
PDF.save(title + ".pdf");
});
} function addImage(PDF, pageData, position, imgWidth, imgHeight, currentHeight) {
PDF.addImage(pageData, "JPEG", 5, position + 5, imgWidth, imgHeight); // 增加偏移量
PDF.setFillColor(255, 255, 255);
PDF.rect(0, 0, 210, 4, "F"); // 添加页眉遮挡
PDF.rect(0, currentHeight + 5, 210, Math.ceil(292 - currentHeight), "F"); // 添加页脚遮挡
}

成果展示

不同缩放下导出 PDF 对比:

每行一个子元素

每行多个子元素

Vue 实现 PDF 导出功能的更多相关文章

  1. vue实现pdf导出,解决生成canvas模糊等问题

    最近公司项目需要,利用vue实现pdf导出,从而保存到本地打印出来,说起来好像也很容易,具体要怎么实现呢? 1 .我们要添加两个模块 第一个.将页面html转换成图片 npm install --sa ...

  2. thinkphp-PHP实现pdf导出功能

    Thinkphp框架引用tcpdf插件,插件下载地址:待续... 代码编写前先引入tcpdf整个文件夹到项目目录的ThinkPHP文件夹下 如:/ThinkPHP/Library/Vendor/tcp ...

  3. ubuntu 16.04.3配置MinDoc的PDF导出功能

    1. 安装 wkhtmltopdf,然后reboot sudo apt-get install wkhtmltopdf 2. 进入cd /usr/bin 测试一下,看目录下有没有pdf生成 wkhtm ...

  4. vue + element-ui实现简洁的导入导出功能

    1.安装ElementUI模块 cnpm install element-ui -S 2.在main.js中引入 import ElementUI from 'element-ui' import ' ...

  5. asp.net导出excel-一行代码实现excel、xml、pdf、word、html、csv等7种格式文件导出功能而且美观-SNF快速开发平台

    分享: 腾讯微博  新浪微博   搜狐微博   网易微博  腾讯朋友  百度贴吧  豆瓣   QQ好友  人人网 作者:王春天  原文地址:http://www.cnblogs.com/spring_ ...

  6. vue+element-ui的简洁导入导出功能

    1.前段后台管理系统中数据展示一般都是用表格,表格会涉及到导入和导出;原生js导出excel2.导入是利用element-ui的Upload 上传组件; <el-upload class=&qu ...

  7. React项目实现导出PDF的功能

    在做web项目中,有时候会遇到pdf导出的需求,现根据之前在公司的React项目中遇到的导出PDF需求,整理一个demo出来. 导出PDF需要用到两个依赖包:html2canvas.jspdf 1.安 ...

  8. 解决highCharts导出功能汉化问题

    本文以highCharts中文网上的例子为原型,处理解决highCharts导出功能为英文的问题. 我们使用highCharts当然希望所有提示或文本都是中文的了,但是highCharts的默认语言是 ...

  9. 前端vue实现pdf文件的在线预览

    3.前端vue实现pdf文件的在线预览 我是通过 <iframe> 标签就可以满足我工作的 pdf预览需求 如果<iframe> 无法满足需求 , 可以使用pdf.js这个插件 ...

  10. SpringCloud微服务实战——搭建企业级开发框架(三十):整合EasyExcel实现数据表格导入导出功能

      批量上传数据导入.数据统计分析导出,已经基本是系统必不可缺的一项功能,这里从性能和易用性方面考虑,集成EasyExcel.EasyExcel是一个基于Java的简单.省内存的读写Excel的开源项 ...

随机推荐

  1. 神州笔记本 win11 节能模式 供电不足 自动关机

    刚刚买了一个神州笔记本没几天,用着用着就出现问题了. 本人使用电脑有个极为不好的习惯,那就是会一次性打开特别多的应用,然后不关,一直留着,这个习惯虽然不好但也是一直没有啥问题的,不过最近换了个新的笔记 ...

  2. 如何构建“集成神经网络”“Ensemble neural network”

    参考: https://arxiv.org/pdf/1603.05691.pdf 本文要讨论的是如何构建"集成神经网络"("Ensemble neural network ...

  3. 自然语言处理:通过API调用各大公司的机器翻译开放平台

    国内大公司做机器翻译做的比较好的有讯飞和百度,这里给出这两个公司机器翻译的开放平台API的介绍: 讯飞开放平台: 链接:https://www.xfyun.cn/doc/nlp/xftrans_new ...

  4. 【转载】 推荐系统 EE 问题与 Bandit 算法

    原文地址: https://toutiao.io/posts/584etm/preview ------------------------------------------------------ ...

  5. lua环境配置与编译

    1.背景 2.安装lua 官方下载地址:https://joedf.ahkscript.org/LuaBuilds/ 下载后解压即可 解压后: 配置环境变量: 检查是否安装成功: 如果能输出版本号,则 ...

  6. 多线程之interrupt与优雅停止一个线程

    1.背景 在实际开发中,我们可能会遇到终止某个线程的场景, 比如不断扫描数据库的发货订单时,这时候需停止扫描, 当然我们不能把程序关了,我们只希望停止扫描数据库这一个线程, 那么应该怎么办了? 这就可 ...

  7. Java RMI技术详解与案例分析

    Java RMI(Remote Method Invocation)是一种允许Java虚拟机之间进行通信和交互的技术.它使得远程Java对象能够像本地对象一样被访问和操作,从而简化了分布式应用程序的开 ...

  8. Win32 状态栏用法

    WIN32 状态控件用法 1.创建控件 状态栏类名: STATUSCLASSNAME #define STATUSCLASSNAMEW        L"msctls_statusbar32 ...

  9. mariadbl数据库环境迁移

    前言: 数据库的主要作用就是对数据进行保存和维护,所以备份数据是数据库管理中最常用的操作.为了防止数据库意外崩溃或硬件损伤而导致的数据丢失,数据库系统提供了备份和恢复策略.保证数据安全的最重要的一个措 ...

  10. AI的那些名词

    AI 是什么? Artificial Intelligence,即人工智能,1956年于Dartmouth学会上提出,一种旨在以类似人类反应的方式对刺激做出反应并从中学习的技术,其理解和判断水平通常只 ...