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
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
