Frontend Development 12 min read

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.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Implementing Multi‑Page Print Preview and PDF Export in Vue 3 with vue3‑print‑nb, html2canvas, and jsPDF

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

frontendPDFhtml2canvasVue3print()jsPDF
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.