目录
源码:
import html2canvas from "html2canvas";
import "./fonts/NotoSansCJKjp-Regular-normal";
import JsPDF from "jspdf";
var _ = require("lodash");
const adhereHeight = 300; // 假如你希望有个元素距离底部${adhereHeight}换行
let print = ({
id,
name,
needSave = true,
fullCover = false,
addPageIDArr = [],
callback = () => {},
drawPaddingHeight = 0,
drawCallback = () => {},
fileCallback = () => {}
}) => {
if (!id) {
throw new Error("element id is required");
}
// 计算需要截断的id元素
const marks = [];
addPageIDArr.map(item => {
try {
let e = document.getElementById(item);
let style = getComputedStyle(e, null);
let markHeight =
e.offsetTop -
item.offsetHeight -
style.getPropertyValue("border-top-width").slice(0, -2) * 1 -
style.getPropertyValue("padding-top").slice(0, -2) * 1;
marks.push(markHeight);
} catch (error) {}
});
// 计算需要截断的className 是addPage的元素
let addPage = document.getElementsByClassName("addPage");
for (const item of addPage) {
let style = getComputedStyle(item, null);
let markHeight =
item.offsetTop -
// style.offsetHeight -
style.getPropertyValue("border-top-width").slice(0, -2) * 1 -
style.getPropertyValue("padding-top").slice(0, -2) * 1 -
style.getPropertyValue("margin-top").slice(0, -2) * 1;
marks.push(markHeight);
}
// 去重,防止有地方算了两遍,截断两次,生成一个空白页
const breakPositionArr = [...new Set(marks)];
breakPositionArr.sort((a, b) => {
return a - b;
});
const adhereArr = [];
let adhereMax = 0;
let adhere = document.getElementsByClassName("adhere");
for (const item of adhere) {
let adhereheight = Number(item.dataset.adhereheight) || 300;
let style = getComputedStyle(item, null);
let markHeight =
item.offsetTop -
// item.offsetHeight -
style.getPropertyValue("border-top-width").slice(0, -2) * 1 -
style.getPropertyValue("padding-top").slice(0, -2) * 1 -
style.getPropertyValue("margin-top").slice(0, -2) * 1;
let option = {
mark: markHeight,
adhereheight
};
adhereArr.push(option);
}
adhereArr.sort((a, b) => {
return a.mark - b.mark;
});
const ele = document.getElementById(id);
html2canvas(ele, {
// dpi: 900,
scale: 1,
width: 900,
useCORS: true, //允许canvas画布内 可以跨域请求外部链接图片, 允许跨域请求。
background: "#fff", //如果指定的div没有设置背景色会默认成黑色,这里是个坑
allowTaint: true // 允许请求图片地址
}).then(canvas => {
let H = canvas.height;
//未生成pdf的html页面高度
var pdfWidth = canvas.width;
var pdfHeight = canvas.height;
var a4Width = 595.28;
var a4Height = 841.89;
let paddingHeight = drawPaddingHeight;
let paddingWidth = (
(paddingHeight / 841.89).toFixed(4) * 595.28
).toFixed(0);
const radio = 0.8;
//一页pdf显示html页面生成的canvas高度;
//pdf页面偏移
var position = 0;
var pageData = canvas.toDataURL("image/png", 1.0);
// canvas.toBlob(blob => {
// console.log(URL.createObjectURL(blob));
// });
var pdf = new JsPDF("x", "pt", "a4");
// pdf.addFont(
// "./fonts/NotoSansCJKjp-Regular.ttf",
// "NotoSansCJKjp-Regular",
// "normal"
// );
pdf.setFont("NotoSansCJKjp-Regular");
pdf.setDisplayMode("fullwidth", "continuous", "FullScreen");
var pdfName = `${name}-${new Date().getTime()}`;
var a4HeightRef = Math.floor((pdfWidth / a4Width) * a4Height);
let num = 0;
function createImpl(canvas) {
let canvas1 = document.createElement("canvas"),
height;
if (fullCover && num == 0) {
a4HeightRef = Math.floor((pdfWidth / a4Width) * a4Height);
} else {
a4HeightRef =
Math.floor((pdfWidth / a4Width) * a4Height) -
paddingHeight * 0;
}
let context = canvas.getContext("2d", { willReadFrequently: true });
if (pdfHeight > 0) {
// if (pdfHeight > a4HeightRef) { // 如果只剩一页,就直接打印,影响了id分页,所以注释
if (true) {
let i, isAdhere, isAdhereFilterArr;
isAdhereFilterArr = adhereArr.filter(item => {
return (
item.mark > position &&
item.mark <= position + a4HeightRef &&
position + a4HeightRef - item.mark <=
Number(item.adhereheight)
);
});
isAdhere = isAdhereFilterArr.length > 0;
let isInSection =
breakPositionArr.filter(item => {
return (
item >= position &&
item <= position + a4HeightRef
);
}).length > 0;
let clip = false;
if (isAdhere) {
for (i = position; i <= position + a4HeightRef; i++) {
let here = adhereArr.filter(item => {
return item.mark == i;
});
if (here.length > 0 && here[0].mark == i) {
let line =
(here[0] && here[0].adhereheight) ||
adhereHeight;
if (position + a4HeightRef - i <= line) {
adhereArr.splice(here, 1);
clip = true;
break;
}
}
}
i -= 1;
}
if (isInSection) {
for (i = position; i <= position + a4HeightRef; i++) {
if (breakPositionArr.includes(i)) {
breakPositionArr.splice(
breakPositionArr.indexOf(i),
1
);
clip = true;
break;
}
}
// i += 2;
}
if (!clip) {
for (i = position + a4HeightRef; i >= position; i--) {
let data = context.getImageData(0, i, pdfWidth, 1)
.data;
let chunkData = _.chunk(data, 4);
// 计算一行像素中出现最多的颜色和次数
let obj = {};
let max = 0;
let maxItem = "";
chunkData.map(item => {
let key = `${item[0]},${item[1]},${item[2]},${item[3]}`;
if (obj[key] !== undefined) {
obj[key]++;
if (obj[key] > max) {
max = obj[key];
maxItem = key;
}
} else {
obj[key] = 1;
}
});
// if (i >= H - 20 && i <= H) {
// console.log(i, H);
// console.log(max);
// console.log(maxItem);
// }
/**
* @param {0:Number} param - R
* @param {1:Number} param - G
* @param {2:Number} param - B
* @param {3:Number} param - A
*/
let maxItemArr = maxItem.split(",");
// 假如一行中出现次数最多的是白色,必须全行都是白色,假如不是白色,超过90%就截断
// 虽然统计出来了颜色,但是实际并没有用到,因为不需要对特殊颜色特殊处理
if (
maxItemArr[0] == 255 &&
maxItemArr[1] == 255 &&
maxItemArr[2] == 255
) {
if (max >= chunkData.length - 8) {
clip = true;
break;
}
} else if (
maxItemArr[0] == 53 &&
maxItemArr[1] == 198 &&
maxItemArr[2] == 191
) {
if (max >= chunkData.length * 0.92) {
clip = true;
break;
}
} else {
if (max == chunkData.length) {
clip = true;
break;
}
}
}
i += 1;
}
// console.log(i - position);
// i += 1;
// 计算这次截断的图片高度,下边要减去,计算还剩多高的图片没有处理
// height = Math.round(i - position);
height =
Math.round(i - position) ||
Math.min(pdfHeight, a4HeightRef);
if (height <= 0) {
height = a4HeightRef;
}
} else {
height = pdfHeight;
}
canvas1.width = pdfWidth;
canvas1.height = height;
var ctx = canvas1.getContext("2d");
// 填充白色,默认生成A4纸大小的canvas,然后把切开的图片放上去,如果没有放上去,会变成透明背景,透明色在pdf上显示为黑色
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, canvas1.width, canvas1.height);
ctx.drawImage(
canvas,
0,
position,
pdfWidth,
height,
0,
0,
pdfWidth,
height
);
// canvas1.toBlob(file => {
// console.log(URL.createObjectURL(file));
// });
let same = true;
for (let index = 0; index < canvas1.height; index++) {
let data = context.getImageData(0, index, canvas1.width, 1)
.data;
let result = data.find(item => {
return item !== 255;
});
if (result !== undefined) {
same = false;
break;
}
}
if (position != 0 && !same) {
pdf.addPage();
}
if (!same) {
num += 1;
drawImageToPdf({
canvas: canvas1,
a4Width,
a4Height,
paddingWidth,
paddingHeight,
fullCover,
pdf,
pageNum: num,
callback,
drawCallback
});
}
// pdf.setFontSize(20);
// pdf.setTextColor(255, 0, 0);
// pdf.text('end!', 10, (a4Width / canvas1.width) * height - 0);
// console.log(pdfHeight, height);
pdfHeight -= height;
position += height;
if (pdfHeight > 0) {
setTimeout(createImpl, 0, canvas);
// return createImpl(canvas);
} else {
callback(-1);
const filename = `${pdfName}.pdf`;
const blob = pdf.output("blob", { filename });
const file = new File([blob], filename, {
type: blob.type
});
fileCallback(file);
if (needSave) {
pdf.save(filename);
}
}
}
}
//当内容未超过pdf一页显示的范围,无需分页
if (pdfHeight < a4HeightRef) {
drawImageToPdf({
canvas,
a4Width,
a4Height,
paddingWidth,
paddingHeight,
fullCover,
pdf,
pageNum: 1,
callback,
drawCallback
});
const filename = `${pdfName}.pdf`;
const blob = pdf.output("blob", { filename });
const file = new File([blob], filename, {
type: blob.type
});
fileCallback(file);
if (needSave) {
pdf.save(pdfName + ".pdf");
}
} else {
try {
pdf.deletePage(0);
setTimeout(createImpl, 0, canvas);
// createImpl(canvas);
} catch (err) {
console.log(err);
}
}
});
};
function drawImageToPdf({
canvas,
a4Width,
a4Height,
paddingWidth,
paddingHeight,
fullCover,
pdf,
pageNum,
callback,
drawCallback
}) {
let R =
1 -
(
(paddingWidth * 2) /
((a4Width / canvas.width) * canvas.width)
).toFixed(4);
let x1 = 0 + paddingWidth,
y1 = 0 + paddingHeight,
// x2 = a4Width - paddingWidth * 2,
x2 = (a4Width / canvas.width) * canvas.width - paddingWidth * 2,
y2 = (a4Width / canvas.width) * canvas.height * R;
// y2 = (a4Width / canvas1.width) * height;
if (fullCover && pageNum == 1) {
x1 = 0;
y1 = 0;
x2 = a4Width;
y2 = (a4Width / canvas.width) * canvas.height;
}
pdf.addImage(canvas.toDataURL("image/jpeg", 1.0), "JPEG", x1, y1, x2, y2);
callback(-1);
drawCallback({
page: pageNum,
a4Width: 595.28,
a4Height: 841.89,
imgWidth: x2,
imgHeight: y2,
pdf: pdf,
paddingHeight: paddingHeight,
paddingWidth: (a4Width - x2) / 2
});
}
export default print;
调用:
可以传入id或者在需要的地方添加'addPage'class类名
<template>
<div @click="P">打印</div>
<div id="box">
<div
v-for="(item, index) in 27"
:class="[
'item',
`item${index + 1}`,
(index + 1) % 5 == 0 ? `addPage` : '',
]"
:id="`item${index + 1}`"
>
{{ item }}
</div>
<div v-for="(item, index) in 25" :class="['item']">
{{ item }}
</div>
</div>
</template>
假如需要在低于xx像素使一个换页,则加入 adhere 类名,默认高度为300(可以在js第5行修改),假如需要自定义高度,则在当前元素上添加 data-adhereheight 的自定义属性。
<div
class="result adhere"
data-adhereheight="100"
>
123
</div>
let P = () => {
/**
* @param {string} 'box' - 打印的盒子id
* @param {string} '1' - 保存的文件名(1.pdf)
* @param {string[]} param - 假如中间有表格需要另起一页,传入他的id,他将在下一页打印
* @param {function:void} param - 当前打印第几页,假如单页打印,会直接返回-1,表示图片处理完毕,开始生成pdf文件
*/
_print({
id: "healthPalnPDF",
name: "健康管理方案",
fullCover: true,
drawPaddingHeight:0, // 需要在pdf上下每边留出多大空白
addPageIDArr: [], // 需要分页的id类名
callback: num => {
console.log(num);
// console.log("正在打印" + num + "页");
if (num !== -1) {
that.loadingText = `正在生成第${num}页`;
loading.setText(`正在生成第${num}页`);
} else {
that.loadingText = `正在生成PDF`;
// setTimeout(() => {
that.loading = false;
// }, 500);
loading.close();
}
},
drawCallback: ({
page,
a4Width,
a4Height,
imgWidth,
imgHeight,
pdf
}) => {
pdf.setFontSize(14);
pdf.setTextColor(0, 0, 0);
let text = `第 ${page} 页`;
pdf.text(text, a4Width - 50, a4Height - 20);
},
needSave: v ? true : false,
fileCallback: file => {
// console.log(file);
}
});
};
node版本:14.16.1 || 16.19.0
包版本:
"html2canvas": "^1.4.1",
"jspdf": "^2.5.1",