导出 PDF
1 安装依赖
shell
pnpm install html2canvas
pnpm install jsPDF2 主要流程
- 使用
html2canvas将 HTML 元素渲染为 canvas - 根据 A4 页面尺寸和 canvas 尺寸,计算分页逻辑
- 通过检测空白行,找到安全的分页点,防止内容截断
- 将每页内容添加到 PDF,并保存文件
3 代码详解
3.1 初始化与 canvas 渲染
ts
const exportToPDF = async () => {
await nextTick(); // 确保DOM更新完成后再执行后续的逻辑,方法防止捕获到为渲染完成的HTML内容
const content = previewRef.value?.resumeContentRef;
if (!content) return;
const canvas = await html2canvas(content, {
scale: 3,
useCORS: true,
backgroundColor: "#fff",
allowTaint: true,
dpi: window.devicePixelRatio * 4,
windowWidth: content.scrollWidth,
windowHeight: content.scrollHeight,
scrollX: 0,
scrollY: 0,
});
// ...
};await nextTick()确保 DOM 更新完成后再执行后续逻辑,防止捕获到未渲染完成的 HTML 内容。previewRef.value?.resumeContentRef获取 DOM 元素(Vue 的 ref),表示需要导出的 HTML 内容。html2canvas:- 将
content渲染为 canvas 对象。 - 配置参数:
scale: 3放大 3 倍以提高清晰度useCORS: true和allowTaint: true:支持跨域资源(如图片)backgroundColor: "#fff":设置 canvas 背景为白色。dpi: window.devicePixelRatio * 4:进一步提高分辨率。windowWidth和windowHeight:设置渲染窗口的尺寸为内容的滚动宽高,确保捕获完整内容。scrollX: 0和scrollY: 0:确保从内容左上角开始渲染。
- 将
3.2 A4 页面与缩放设置
ts
const pdf = new jsPDF("p", "pt", "a4");
const margin = 13 * 2.83465;
const a4Width = 595.28;
const a4Height = 841.89;
const pageContentWidth = a4Width - margin * 2;
const pageContentHeight = a4Height - margin * 2;
const canvasWidth = canvas.width;
const canvasHeight = canvas.height;
const scale = pageContentWidth / canvasWidth;
const pagePixelHeight = pageContentHeight / scale;- A4 尺寸:
- A4 纸张标准尺寸为 210mm x 297mm,单位为点(pt),即
595.28 x 841.89(1mm ≈ 2.83465pt)。 margin = 13 \* 2.83465:设置 13mm 的边距,转换为点(约 36.85pt)。pageContentWidth和pageContentHeight:计算 A4 页面内容区域的宽高,减去两侧边距。
- A4 纸张标准尺寸为 210mm x 297mm,单位为点(pt),即
- 缩放比例:
scale = pageContentWidth / canvasWidth:根据 canvas 宽度和页面内容宽度,计算缩放比例,确保 canvas 内容适配 A4 页面宽度。pagePixelHeight = pageContentHeight / scale:计算一页 A4 在 canvas 中对应的高度(以像素为单位)。
- 变量:
canvasWidth和canvasHeight:canvas 的实际宽高(像素)。positionY:当前处理的 canvas 垂直偏移量,初始为 0。
3.3 空白行检测函数
ts
const isBlankLine = (y: number) => {
const ctx = canvas.getContext("2d")!;
for (let x = 0; x < canvas.width; x += 10) {
const pixel = ctx.getImageData(x, y, 1, 1).data;
if (pixel[0] !== 255 || pixel[1] !== 255 || pixel[2] !== 255) {
return false;
}
}
return true;
};- 检查 canvas 在指定
y坐标的行是否为白色(空白行)。 - 使用
getImageData(x,y,1,1)获取单个像素的 RGBA 值。 - 这里为了提高效率,每个 10 个像素检查一次(
x += 10),而不是检查每一像素。 - 如果所有检查的像素均为
255(白色),返回true,表示空白行;否则返回false。
3.4 寻找安全分页点
ts
const findSafeSplitPosition = (startY: number, endY: number) => {
let blankLineCount = 0;
for (let y = Math.floor(endY); y >= Math.floor(startY); y--) {
if (isBlankLine(y)) {
blankLineCount++;
if (blankLineCount >= 5) return y;
} else {
blankLineCount = 0;
}
}
return endY;
};- 再
startY和endY,从底部向上扫描,寻找连续 5 行空白的行(blankLineCount >= 5)。 - 如果寻找到连续 5 行空白,就返回该行
y坐标作为安全分页点。 - 如果没有找到合适的空白区域,返回
endY(默认分页点)。 - 通过寻找连续空白行,确保分页点位于内容之间的空隙,避免文字或图片被切断。
3.5 分页与 PDF 输出
ts
while (positionY < canvasHeight) {
let currentPagePixelHeight = Math.min(
pagePixelHeight,
canvasHeight - positionY
);
if (positionY + currentPagePixelHeight < canvasHeight) {
const safeY = findSafeSplitPosition(
positionY,
positionY + currentPagePixelHeight
);
currentPagePixelHeight = safeY - positionY;
if (currentPagePixelHeight <= 0) {
currentPagePixelHeight = pagePixelHeight;
}
}
const pageCanvas = document.createElement("canvas");
pageCanvas.width = canvasWidth;
pageCanvas.height = currentPagePixelHeight;
const ctx = pageCanvas.getContext("2d")!;
ctx.fillStyle = "#ffffff";
ctx.fillRect(0, 0, pageCanvas.width, pageCanvas.height);
ctx.drawImage(
canvas,
0,
positionY,
canvasWidth,
currentPagePixelHeight,
0,
0,
canvasWidth,
currentPagePixelHeight
);
if (positionY > 0) pdf.addPage();
pdf.addImage(
pageCanvas.toDataURL("image/jpeg", 1.0),
"JPEG",
margin,
margin,
pageContentWidth,
pageContentHeight * (currentPagePixelHeight / pagePixelHeight)
);
positionY += currentPagePixelHeight;
}
pdf.save("resume.pdf");- 分页循环:
while (positionY < canvasHeight):只要还有未处理的 canvas 高度,继续分页。currentPagePixelHeight:计算当前页的高度,取pagePixelHeight和剩余高度(canvasHeight - positionY)的最小值。
- 安全分页:
- 如果当前页不是最后一页(
positionY + currentPagePixelHeight < canvasHeight),调用findSafeSplitPosition寻找安全分页点。 currentPagePixelHeight = safeY - positionY:调整当前页高度为安全分页点。- 如果
currentPagePixelHeight <= 0(极少数情况,如空白区域检测失败),恢复默认页高。
- 如果当前页不是最后一页(
- 创建分页 canvas:
- 创建一个临时 canvas(
pageCanvas),尺寸为canvasWidth x currentPagePixelHeight。 - 设置背景为白色(
ctx.fillRect),然后绘制原始 canvas 的指定区域(从positionY开始)。
- 创建一个临时 canvas(
- 添加到 PDF:
- 将
pageCanvas转换为 JPEG 格式,添加到 PDF。 pdf.addImage的参数:margin, margin:左右和上边距。pageContentWidth:图像宽度适配页面内容宽度。pageContentHeight \* (currentPagePixelHeight / pagePixelHeight):按比例计算图像高度,确保纵向缩放一致。
- 如果不是第一页(
positionY > 0),调用pdf.addPage()添加新页面。
- 将
- 更新偏移量:
positionY += currentPagePixelHeight:更新 canvas 的垂直偏移量。- 循环继续,直到
positionY >= canvasHeight。
