vue2 html转图片/PDF示例

该文章已生成可运行项目,

核心库为:snapDOM、html-to-image、jsPDF

处理流程为DOM元素=>image=>pdf

封装工具函数为:

import { jsPDF as JsPDF } from 'jspdf'
import { snapdom } from '@zumer/snapdom'
import { toJpeg } from 'html-to-image'

/**
 * 单页截图工具
 * @param {HTMLElement|string} target - DOM元素或选择器
 * @param {object} options - 配置项
 * @param {number} [options.scale=1] - 缩放比例
 * @param {boolean} [options.preview=false] - 是否预览截图(默认关闭)
 * @param {boolean} [options.downLoad=false] - 是否下载截图(默认关闭)
 * @param {string} [options.documentName=''] - 文档名称
 * @returns - 返回PNG的Base64字符串
 */
export const capturePageAsPNG = async(target, options = {}) => {
  const { scale = 1, preview = false, downLoad = false, documentName = '' } = options
  const el = typeof target === 'string' ? document.querySelector(target) : target
  if (!el) throw new Error('无效的DOM元素')
  await new Promise(resolve => setTimeout(resolve, 50))
  const snapshot = await snapdom(el, { scale, compress: false, embedFonts: true })
  const imgBase64 = await snapshot.toPng()

  if (preview) {
    const img = new Image()
    img.src = imgBase64
    img.style.maxWidth = '100%'
    document.body.appendChild(img)
  }
  if (downLoad) {
    await snapshot.download({ format: 'jpg', filename: `${documentName}` })
  }

  return imgBase64 // 返回单页PNG的Base64
}

/**
 * 纸张尺寸映射表(300dpi分辨率)
 * 键:纸张类型
 * 值:包含宽高尺寸的对象(单位:像素)
 */
const jpegPaperSizeMap = {
  a4: { width: 2480, height: 3508 }, // A4尺寸:210mm×297mm → 2480×3508像素
  a5: { width: 1748, height: 2480 }, // A5尺寸:148mm×210mm → 1748×2480像素
  letter: { width: 2550, height: 3300 } // 美国信纸尺寸:8.5×11英寸 → 2550×3300像素
}

/**
 * 将HTML元素转换为JPEG格式的Base64图片
 * @param {string|HTMLElement} target - DOM元素或CSS选择器字符串
 * @param {Object} options - 配置选项
 * @param {number} [options.quality=1] - 图片质量(0-1)
 * @param {string} [options.paperType='a4'] - 纸张类型('a4'|'a5'|'letter')
 * @param {boolean} [options.preview=false] - 是否在页面预览生成的图片
 * @param {boolean} [options.downLoad=false] - 是否自动下载图片
 * @param {string} [options.documentName=''] - 下载时的文件名
 * @returns {Promise<string|null>} 返回JPEG的Base64字符串或null(失败时)
 */
export const convertHtmlToJpegBase64 = async(
  target, // 支持选择器字符串或DOM元素
  options = {}
) => {
  const {
    quality = 1,
    paperType = 'a4',
    preview = false,
    downLoad = false,
    documentName = ''
  } = options
  try {
    const el = typeof target === 'string' ? document.querySelector(target) : target
    if (!el) throw new Error('无效的DOM元素')
    const { width: targetWidth, height: targetHeight } = jpegPaperSizeMap[paperType]
    const realWidth = el.scrollWidth
    const realHeight = el.scrollHeight
    let scale = 1
    let extraStyle = {}
    // 超出页面尺寸才缩放
    if (realHeight > targetHeight || realWidth > targetWidth) {
      scale = Math.min(targetWidth / realWidth, targetHeight / realHeight)
      extraStyle = {
        transform: `scale(${scale})`,
        transformOrigin: 'top left'
      }
    }
    // 添加微小延迟确保样式应用
    await new Promise(resolve => setTimeout(resolve, 50))
    const imgBase64 = await toJpeg(el, {
      backgroundColor: '#FFF',
      width: realWidth,
      height: realHeight,
      style: extraStyle,
      quality
    })
    // 预览功能
    if (preview) {
      const img = new Image()
      img.src = imgBase64
      img.style.maxWidth = '100%'
      document.body.appendChild(img)
    }
    // 下载功能
    if (downLoad) {
      const link = document.createElement('a')
      link.href = imgBase64
      link.download = documentName || 'document.jpg'
      link.click()
    }
    return imgBase64
  } catch (e) {
    console.error('转换HTML为JPEG出错:', e)
    return null
  }
}

/**
 * 将多页PNG合并为PDF
 * @param {string[]} pngBase64Array - 多页PNG的Base64数组
 * @param {object} options - PDF配置
 * @param {string} [options.title='PDF'] - PDF标题
 * @param {'a3'|'a4'} [options.paperType='a4'] - 纸张类型
 * @param {'p'|'l'} [options.orientation] - 强制方向(默认自动选择)
 * @param {boolean} [options.download=false] - 是否自动下载PDF
 * @param {string} [options.filename=''] - 下载的文件名
 * @returns {string} - PDF的Base64字符串
 */
export const convertPNGsToPDF = (pngBase64Array, options = {}) => {
  const { title = 'PDF', paperType = 'a4', orientation, download = false, filename = '' } = options
  if (!pngBase64Array?.length) throw new Error('PNG数组不能为空')

  // 自动决定方向:A3横向,A4纵向(可被orientation覆盖)
  const finalOrientation = orientation || (paperType === 'a3' ? 'l' : 'p')
  const pdf = new JsPDF(finalOrientation, 'mm', paperType)

  const pageSize = pdf.internal.pageSize
  const pageWidth = pageSize.getWidth() - 20 // 左右留白
  const pageHeight = pageSize.getHeight() - 20 // 上下留白

  pdf.setProperties({ title })

  pngBase64Array.forEach((pngBase64, index) => {
    if (index > 0) pdf.addPage()
    pdf.addImage(pngBase64, 'PNG', 10, 10, pageWidth, pageHeight)
  })
  // 新增下载控制
  if (download) {
    downloadPDF(pdf, filename || `${options.title || 'document'}`)
  }

  return pdf.output('datauristring')
}

/**
 * 下载PDF文件
 * @param {string|Blob|jsPDF} pdfSource - PDF数据源(Base64字符串/Blob对象/jsPDF实例)
 * @param {string} [filename='document.pdf'] - 下载的文件名
 */
export const downloadPDF = (pdfSource, filename = 'document.pdf') => {
  // 确保文件名以.pdf结尾
  if (!filename.toLowerCase().endsWith('.pdf')) {
    filename += '.pdf'
  }

  let blob
  if (typeof pdfSource === 'string') {
    // 处理Base64字符串
    const byteString = atob(pdfSource.split(',')[1])
    const ab = new ArrayBuffer(byteString.length)
    const ia = new Uint8Array(ab)
    for (let i = 0; i < byteString.length; i++) {
      ia[i] = byteString.charCodeAt(i)
    }
    blob = new Blob([ab], { type: 'application/pdf' })
  } else if (pdfSource instanceof Blob) {
    // 直接使用Blob对象
    blob = pdfSource
  } else if (pdfSource?.output) {
    // 处理jsPDF实例
    blob = pdfSource.output('blob')
  } else {
    throw new Error('不支持的PDF数据源类型')
  }

  // 创建下载链接
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = filename
  document.body.appendChild(a)
  a.click()

  // 清理
  setTimeout(() => {
    document.body.removeChild(a)
    URL.revokeObjectURL(url)
  }, 100)
}

辅助工具函数DOM稳定检测器:

/**
 * 等待DOM稳定(Vue 2兼容版)
 * @param {string} selector - 选择器
 * @param {number} [timeout=1000] - 超时时间(ms)
 * @returns {Promise<void>}
 */
export const waitForDOMStable = function(selector, timeout) {
  timeout = timeout || 1000 // 默认超时1秒

  return new Promise(function(resolve) {
    const targetNode = document.querySelector(selector)
    if (!targetNode) return resolve()

    let timer
    const observer = new MutationObserver(function() {
      clearTimeout(timer)
      timer = setTimeout(function() {
        observer.disconnect()
        resolve()
      }, 200) // 100ms内无变化视为稳定
    })
    observer.observe(targetNode, {
      childList: true,
      subtree: true,
      attributes: true,
      characterData: true
    })

    // 超时保护
    setTimeout(function() {
      observer.disconnect()
      resolve()
    }, timeout)
  })
}

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值