Canvas to PDF — 使用 JavaScript 将 HTML5 Canvas 导出为 PDF
使用 JavaScript 将任意 HTML5 Canvas 内容导出为 PDF 文档。此演示展示了如何使用 jsPDF 将 Konva 舞台转换为可下载的 PDF 文件,并支持高质量渲染和可选中文本。
该方法分四个步骤:生成画布内容,将画布导出为图像,把图像插入 PDF 文档,然后保存。为了获得最佳效果,有两个提示:
高质量导出: 在将画布转换为图像时使用 pixelRatio 属性——详情请参见 高质量导出指南。
PDF 中的可选中文本: 尽管画布是作为图像添加的,你仍可以手动将文本节点插入其下方的 PDF 图层中。文本不会可见(因为它位于图像后面),但仍可被选择和搜索。PDF 中的文本渲染与 Konva 不同,因此复杂样式可能需要调整。
说明:查看下面的画布,然后点击按钮将其保存为 PDF。
- Vanilla
- React
- Vue
import Konva from 'konva';
import { jsPDF } from 'jspdf';
// 创建用于 PDF 导出的按钮
const saveButton = document.createElement('button');
saveButton.textContent = '保存为 PDF';
saveButton.style.position = 'absolute';
saveButton.style.top = '5px';
saveButton.style.left = '5px';
document.body.appendChild(saveButton);
// 创建舞台
const width = window.innerWidth;
const height = window.innerHeight;
const stage = new Konva.Stage({
container: 'container',
width: width,
height: height,
});
const layer = new Konva.Layer();
stage.add(layer);
// 添加背景
const back = new Konva.Rect({
width: stage.width(),
height: stage.height(),
fill: 'rgba(200, 200, 200)',
});
layer.add(back);
// 添加带模糊效果的文本
const text = new Konva.Text({
text: '这是达斯·维达',
x: 15,
y: 40,
rotation: -10,
filters: [Konva.Filters.Blur],
blurRadius: 4,
fontSize: 18,
});
text.cache();
layer.add(text);
// 添加箭头
const arrow = new Konva.Arrow({
points: [70, 50, 100, 80, 150, 100, 190, 100],
tension: 0.5,
stroke: 'black',
fill: 'black',
});
layer.add(arrow);
// 添加图像
const imageUrl = 'https://konvajs.org/assets/darth-vader.jpg';
Konva.Image.fromURL(
imageUrl,
function (darthNode) {
darthNode.setAttrs({
x: 200,
y: 50,
scaleX: 0.5,
scaleY: 0.5,
});
layer.add(darthNode);
},
function () {
console.error('加载图像失败');
}
);
// 处理 PDF 导出
saveButton.addEventListener('click', function () {
const pdf = new jsPDF({
orientation: 'landscape',
unit: 'px',
format: [stage.width(), stage.height()],
hotfixes: ['px_scaling'],
});
pdf.setTextColor('#000000');
// First add texts
stage.find('Text').forEach((text) => {
const size = text.fontSize() * 0.75; // convert pixels to points
pdf.setFontSize(size);
pdf.text(text.text(), text.x(), text.y(), {
baseline: 'top',
angle: -text.getAbsoluteRotation(),
});
});
// Then put image on top of texts (so texts are not visible)
pdf.addImage(
stage.toDataURL({ pixelRatio: 2 }),
0,
0,
stage.width(),
stage.height()
);
pdf.save('canvas.pdf');
});
import Konva from 'konva';
import { useEffect, useRef } from 'react';
import { Stage, Layer, Rect, Text, Arrow, Image } from 'react-konva';
import useImage from 'use-image';
import { jsPDF } from 'jspdf';
const App = () => {
const stageRef = useRef(null);
const textRef = useRef(null);
const [darthVaderImage] = useImage('https://konvajs.org/assets/darth-vader.jpg', 'anonymous');
const width = window.innerWidth;
const height = window.innerHeight;
useEffect(() => {
textRef.current?.cache();
}, []);
// 处理 PDF 导出
const handleExport = () => {
if (stageRef.current) {
const stage = stageRef.current;
const pdf = new jsPDF({
orientation: 'landscape',
unit: 'px',
format: [width, height],
hotfixes: ['px_scaling'],
});
pdf.setTextColor('#000000');
// 首先添加文本
stage.find('Text').forEach((text) => {
const size = text.fontSize() * 0.75; // convert pixels to points
pdf.setFontSize(size);
pdf.text(text.text(), text.x(), text.y(), {
baseline: 'top',
angle: -text.getAbsoluteRotation(),
});
});
// 然后在文本上绘制图像(使文本不可见)
pdf.addImage(
stage.toDataURL({ pixelRatio: 2 }),
0,
0,
width,
height
);
pdf.save('canvas.pdf');
} else {
console.error('Stage is not available');
}
};
return (
<div style={{ position: 'relative' }}>
<button
style={{ position: 'absolute', top: '5px', left: '5px', zIndex: 10 }}
onClick={handleExport}
>
保存为 PDF
</button>
<Stage width={width} height={height} ref={stageRef}>
<Layer>
<Rect
width={width}
height={height}
fill="rgba(200, 200, 200)"
/>
<Text
ref={textRef}
text="This is the Darth Vader"
x={15}
y={40}
rotation={-10}
fontSize={18}
filters={[Konva.Filters.Blur]}
blurRadius={4}
/>
<Arrow
points={[70, 50, 100, 80, 150, 100, 190, 100]}
tension={0.5}
stroke="black"
fill="black"
/>
{darthVaderImage && (
<Image
image={darthVaderImage}
x={200}
y={50}
scaleX={0.5}
scaleY={0.5}
/>
)}
</Layer>
</Stage>
</div>
);
};
export default App;
<template>
<div style="position: relative">
<button
style="position: absolute; top: 5px; left: 5px; z-index: 10"
@click="handleExport"
>
保存为 PDF
</button>
<v-stage ref="stageRef" :config="stageConfig">
<v-layer>
<v-rect :config="backgroundConfig" />
<v-text ref="textRef" :config="textConfig" />
<v-arrow :config="arrowConfig" />
<v-image
v-if="darthVaderImage"
:config="imageConfig"
/>
</v-layer>
</v-stage>
</div>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue';
import Konva from 'konva';
import { useImage } from 'vue-konva';
import { jsPDF } from 'jspdf';
const stageRef = ref(null);
const textRef = ref(null);
const [darthVaderImage] = useImage('https://konvajs.org/assets/darth-vader.jpg', 'anonymous');
const width = window.innerWidth;
const height = window.innerHeight;
// 舞台配置
const stageConfig = {
width,
height
};
// 背景配置
const backgroundConfig = {
width,
height,
fill: 'rgba(200, 200, 200)'
};
// 文本配置
const textConfig = {
text: '这是达斯·维达',
x: 15,
y: 40,
rotation: -10,
fontSize: 18,
filters: [Konva.Filters.Blur],
blurRadius: 4
};
// 箭头配置
const arrowConfig = {
points: [70, 50, 100, 80, 150, 100, 190, 100],
tension: 0.5,
stroke: 'black',
fill: 'black'
};
// 图像配置
const imageConfig = computed(() => ({
image: darthVaderImage.value,
x: 200,
y: 50,
scaleX: 0.5,
scaleY: 0.5
}));
onMounted(() => {
// Cache text for blur filter to work
if (textRef.value) {
textRef.value.getNode().cache();
}
});
// Handle PDF export
const handleExport = () => {
if (stageRef.value) {
const stage = stageRef.value.getNode();
const pdf = new jsPDF({
orientation: 'landscape',
unit: 'px',
format: [width, height],
hotfixes: ['px_scaling'],
});
pdf.setTextColor('#000000');
// 首先添加文本
stage.find('Text').forEach((text) => {
const size = text.fontSize() * 0.75; // convert pixels to points
pdf.setFontSize(size);
pdf.text(text.text(), text.x(), text.y(), {
baseline: 'top',
angle: -text.getAbsoluteRotation(),
});
});
// 然后在文本上绘制图像(使文本不可见)
pdf.addImage(
stage.toDataURL({ pixelRatio: 2 }),
0,
0,
width,
height
);
pdf.save('canvas.pdf');
} else {
console.error('Stage is not available');
}
};
</script>
打印输出
上述方法会将栅格图像放入 RGB 文档中。商业打印机通常要求 CMYK 分色、PDF/X 合规性、出血和裁切标记,而浏览器画布不会生成这些内容——这需要在浏览器外部执行渲染步骤。Polotno 是一个基于 Konva 构建的商业设计编辑器 SDK,涵盖编辑器及其导出流程。