Implementing Multi‑Page Print Preview and PDF Export in Vue 3 with vue3‑print‑nb, html2canvas, and jsPDF
This article explains how to achieve paginated print preview and PDF generation in a Vue 3 application by using vue3‑print‑nb for on‑demand printing, html2canvas to capture DOM elements as images, and jsPDF to compose multi‑page A4 PDFs with custom headers, footers, and spacing.
1. Background – The author needed to print HTML content in a Vue 3 project, previously using vue‑print‑nb‑jeecg for Vue 2. The new requirement involves printing dozens of pages and exporting them as a PDF, which introduces challenges around pagination and asynchronous data rendering.
2. Preview Print Implementation – Using the <div id="printMe" style="background:red;">…</div> container and the v-print="'#printMe'" directive, a button triggers the print preview. The code snippet demonstrates the printable markup and the button that calls the library:
<div id="printMe" style="background:red;">
<p>葫芦娃,葫芦娃</p>
<p>一根藤上七朵花</p>
<p>小小树藤是我家 啦啦啦啦 </p>
<p>叮当当咚咚当当 浇不大</p>
<p> 叮当当咚咚当当 是我家</p>
<p> 啦啦啦啦</p>
<p>...</p>
</div>
<button v-print="'#printMe'">Print local range</button>The author notes that the library’s beforeOpenCallback cannot guarantee data loading, so a manual click‑then‑render‑then‑print flow is used.
3. PDF Download – The solution converts the printable DOM to a canvas with html2canvas , then assembles a multi‑page PDF using jsPDF . It defines A4 dimensions, page orientation, header/footer handling, and functions for adding images, blank spaces, and pagination logic. The full implementation is provided below:
import html2canvas from 'html2canvas'
import jsPDF, { RGBAData } from 'jspdf'
/** a4纸的尺寸[595.28,841.89], 单位毫米 */
const [PAGE_WIDTH, PAGE_HEIGHT] = [595.28, 841.89]
const PAPER_CONFIG = {
/** 竖向 */
portrait: {
height: PAGE_HEIGHT,
width: PAGE_WIDTH,
contentWidth: 560
},
/** 横向 */
landscape: {
height: PAGE_WIDTH,
width: PAGE_HEIGHT,
contentWidth: 800
}
}
// 将元素转化为canvas元素
async function toCanvas(element: HTMLElement, width: number) {
if (!element) return { width, height: 0 }
const canvas = await html2canvas(element, {
scale: window.devicePixelRatio * 2,
useCORS: true
})
const { width: canvasWidth, height: canvasHeight } = canvas
const height = (width / canvasWidth) * canvasHeight
const canvasData = canvas.toDataURL('image/jpeg', 1.0)
return { width, height, data: canvasData }
}
export async function outputPDF({ element, footer, header, filename, orientation = 'portrait' as 'portrait' | 'landscape' }) {
if (!(element instanceof HTMLElement)) return
if (!['portrait', 'landscape'].includes(orientation)) {
return Promise.reject(new Error(`Invalid Parameters: orientation must be 'portrait' or 'landscape'`))
}
const [A4_WIDTH, A4_HEIGHT] = [PAPER_CONFIG[orientation].width, PAPER_CONFIG[orientation].height]
const { contentWidth } = PAPER_CONFIG[orientation]
const pdf = new jsPDF({ unit: 'pt', format: 'a4', orientation })
const { width, height, data } = await toCanvas(element, contentWidth)
// header / footer conversion
const { height: tFooterHeight, data: footerData } = footer ? await toCanvas(footer, contentWidth) : { height: 0, data: undefined }
const { height: tHeaderHeight, data: headerData } = header ? await toCanvas(header, contentWidth) : { height: 0, data: undefined }
const baseX = (A4_WIDTH - contentWidth) / 2
const baseY = 15
const originalPageHeight = A4_HEIGHT - tFooterHeight - tHeaderHeight - 2 * baseY
// pagination calculation (simplified for brevity)
const pages = []
let currentY = 0
while (currentY < height) {
pages.push(currentY)
currentY += originalPageHeight
}
for (let i = 0; i < pages.length; i++) {
const pageTop = pages[i]
const pageHeight = Math.min(originalPageHeight, height - pageTop)
pdf.addImage(data, 'JPEG', baseX, baseY + tHeaderHeight, contentWidth, pageHeight)
if (headerData) pdf.addImage(headerData, 'JPEG', 0, 0, contentWidth, tHeaderHeight)
if (footerData) pdf.addImage(footerData, 'JPEG', 0, A4_HEIGHT - tFooterHeight, contentWidth, tFooterHeight)
if (i < pages.length - 1) pdf.addPage()
}
return pdf.save(filename)
}4. Pagination Tricks – CSS rules can hide the browser’s default header/footer and set custom margins when printing:
@page {
size: auto A4 landscape;
margin: 3mm;
}
@media print {
body, html {
height: initial;
padding: 0px;
margin: 0px;
}
}5. Header/Footer Componentization – The author wraps custom header/footer markup into reusable Vue components, extracts shared styles into a pub.scss file, and applies absolute positioning so that UI changes only require updates in the shared stylesheet.
Reference article: https://juejin.cn/post/7323436080312893476
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.