该实现方案是基于html2canvas和jspdf进行开发的,具体参考html2canvas.hertzen.com/configurati… 文档 www.npmjs.com/package/jsp… 文档 以下代码都是可以直接使用的,下载的PDF的效果如下
一、该方案解决了两个痛点:
1、下载页码收到限制
2、分页的时候会截断文字或者图片
二、使用DEMO
下面是原生的JavaScript实现,如果想要Vue实现,可以稍微改动一下即可,如果不会,在留言中告诉我,后续我加上在Vue中实现
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>下载PDF</title>
</head>
<style></style>
<body>
<div style="text-align: left">
<button id="qubtn-download" style="margin: 20px 200px">下载</button>
<div
id="print"
style="
display: flex;
justify-content: center;
max-height: 600px;
width: 595px;
margin-top: 20px;
text-align: center;
overflow: auto;
"
>
<div
id="print-area"
style="width: 595px; height: 100%; text-align: center"
></div>
</div>
</div>
</body>
// html2canvas.min.js 可以通过 https://github.com/niklasvh/html2canvas/releases下载
<script src="./html2canvas.min.js"></script>
<script src="https://unpkg.com/jspdf@latest/dist/jspdf.umd.min.js"></script>
<script src="./downloadPDF.js"></script>
<script>
//下面是两个插件的文档
// https://html2canvas.hertzen.com/configuration 文档
// https://www.npmjs.com/package/jspdf 文档
// 生成测试的节点数据
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const div = document.createElement("div");
div.style.marginBottom = "20px";
div.style.textAlign = "center";
div.innerHTML = "我是用来测试打印的测试数据";
fragment.append(div);
}
document.querySelector("#print-area").append(fragment);
// 注册打印事件
document
.querySelector("#qubtn-download")
.addEventListener("click", function () {
// 调用PDF下载类,传入要下载元素的id即可,不限页码!
// 主要看这里
new DownloadPDF("print-area").init();
});
</script>
</html>
三、实现下载PDF代码
因为注释已经很详细了,如果觉得哪里看不懂有疑问可留言
/**
* @description 该PDF下载类,基于jsPDF 跟 html2canvas两个插件开发,下载页码不受限制,并且分页的时候不会截断文字跟图片
* @author 郭太东
* @param id 需要下载的元素id
*/
class DownloadPDF {
constructor(id) {
this.id = id; // 需要下载的元素id
this.devicePixelRatio = 2; // canvas高度 = 实际的元素高度*this.devicePixelRatio
this.canvasHeight; // html转成canvas计算出的最大理论高度
this.pageTotal; // 当前canvas可生成最多页数
this.canvasW = 850; // 写死canvas的宽度,一定要跟实际元素宽度一致,才能计算准确
this.a4Width = 595.28;
this.a4Height = 841.89; //A4大小,210mm x 297mm,(对应的px:595.28*841.89)四边各保留10mm的边距,显示区域190x277
this.a4HeightRef; //一页pdf显示html页面生成的canvas标准高度
this.canvasCurH; // 当前生成canvas的总高度
this.yPos = 0; // canvas开始绘制的y轴坐标点
this.pageNumber = 0; // pdf底部页码显示
}
// 入口
init() {
this.canvasHeight =
this.devicePixelRatio *
document.querySelector("#" + this.id).clientHeight; // 计算canvas总高度(生成canvas的时候记得写死宽度,宽度跟id是print-area的元素一样)
this.a4HeightRef = Math.floor(
((this.canvasW * this.devicePixelRatio) / this.a4Width) *
(this.a4Height - 22)
);
this.pageTotal = Math.floor(this.canvasHeight / this.a4HeightRef) + 1;
// 第一个canvas 高度, 一个canvas大概能容纳25页左右pdf高度的内容,多了会黑屏无法显示
this.canvasCurH =
this.pageTotal <= 25 ? this.canvasHeight : 25 * this.a4HeightRef;
// jspdf 这是全局使用时候的使用方法
const pdf = new jspdf.jsPDF("p", "pt", "a4"); //A4纸,纵向
pdf.setDisplayMode("fullwidth", "continuous", "FullScreen");
pdf.setFontSize(8); // 设置字体大小
this.downloadPDF(pdf);
}
/**
* @returns {void} 生成canvas 下载pdf
*/
downloadPDF(pdf) {
const _this = this;
html2canvas(document.querySelector("#" + this.id), {
allowTaint: true,
useCORS: true,
height: this.canvasCurH / 2,
width: this.canvasW, // 写死canvas的宽度,一定要跟实际元素宽度一致,才能计算准确
scale: this.devicePixelRatio,
y: this.yPos / 2,
background: "#FFFFFF", //如果指定的div没有设置背景色会默认成黑色,这里是个坑
})
.then((canvas) => {
//未生成pdf的html页面高度
let leftHeight = canvas.height;
// console.log("canvas 宽度", canvas.width);
// console.log("canvas 高度", leftHeight);
let position = 0;
let pageData = canvas.toDataURL("image/jpeg", 1.0);
let index = 0,
canvas1 = document.createElement("canvas"),
height;
function createImpl(canvas) {
if (leftHeight > 0) {
index++;
_this.pageNumber++;
let checkCount = 0;
if (leftHeight > _this.a4HeightRef) {
let i = position + _this.a4HeightRef;
for (i; i >= position; i--) {
let isWrite = true;
for (let j = 0; j < canvas.width; j++) {
let c = canvas.getContext("2d").getImageData(j, i, 1, 1).data;
// 利用像素点颜色判断截断的时机
if (c[0] != 0xff || c[1] != 0xff || c[2] != 0xff) {
isWrite = false;
break;
}
}
if (isWrite) {
// 该像素行是纯白
checkCount++;
if (checkCount >= 2) {
break;
}
} else {
// 该像素行不是纯白
checkCount = 0;
}
}
height =
Math.round(i - position) ||
Math.min(leftHeight, _this.a4HeightRef);
if (height <= 0) {
height = _this.a4HeightRef;
}
} else {
height = leftHeight;
}
// console.log(index, "截图高:", height, "偏移 pos", position);
canvas1.width = canvas.width;
canvas1.height = height;
let ctx = canvas1.getContext("2d");
ctx.drawImage(
canvas,
0,
position,
canvas.width,
height,
0,
0,
canvas.width,
height
);
let y = 10; // 如果是第一页, 上边距 设为0,避免下移导致下边文字被截一半, 如果用在其他场景,酌情进行调整该值,或者设置为0则万事大吉!
if (position != 0 || _this.yPos > 0) {
// y = 10; // 同上调整,建议结合文字的间距调整值
pdf.addPage();
}
pdf.addImage(
canvas1.toDataURL("image/jpeg", 1.0),
"JPEG",
0,
y,
_this.a4Width,
(_this.a4Width / canvas1.width) * height
);
pdf.setPage(_this.pageNumber);
pdf.text(
"Page " + _this.pageNumber,
_this.a4Width / 2 - 20,
_this.a4Height - 5
);
leftHeight -= height;
position += height;
if (leftHeight > 0) {
if (leftHeight < _this.a4HeightRef && index > 24) {
// 取倒数第二页的y轴坐标值,以便多个canvas拼接的时候不会出现页面填不满的情况
_this.yPos += position;
_this.pageTotal = Math.floor(
(_this.canvasHeight - _this.yPos) / _this.a4HeightRef
);
// 下一个 canvas 高度
_this.canvasCurH =
_this.pageTotal <= 25
? _this.canvasHeight - _this.yPos
: 25 * _this.a4HeightRef;
// console.log("继续生成新的canvas画布!!", _this.yPos, _this.pageTotal);
// 如果超出了25页,新建一个canvas画布绘制剩下的元素,直到绘制完所有的元素
_this.downloadPDF(pdf);
} else {
setTimeout(createImpl, 100, canvas);
}
} else {
pdf.save("pdf" + Date.now() + ".pdf");
setTimeout(() => {
alert("下载完成");
}, 1000);
}
}
}
//当内容未超过pdf一页显示的范围,无需分页
if (leftHeight < _this.a4HeightRef) {
let y = 0;
if (_this.yPos > 0) {
y = 10;
pdf.addPage();
}
pdf.addImage(
pageData,
"JPEG",
0,
y,
_this.a4Width,
(_this.a4Width / canvas.width) * leftHeight
);
pdf.save("pdf" + Date.now() + ".pdf");
setTimeout(() => {
alert("下载完成");
}, 1000);
} else {
try {
setTimeout(createImpl, 100, canvas);
} catch (err) {
alert("下载失败");
}
}
})
.catch((e) => {
console.log(e);
alert("下载失败");
});
}
}
欢迎留言交流~