vue+jspdf+html2canvas生成pdf文件

安装依赖
npm install html2canvas --save
npm install jspdf --save    
outputPDF.JS
import jsPDF from 'jspdf';
import html2canvas from 'html2canvas';
const A4_WIDTH = 592.28;
const A4_HEIGHT = 841.89;
/**
 * @param {Object} param
 * @param {HTMLElement} param.element - 需要转换的dom根节点
 * @param {number} [param.contentWidth=550] - 一页pdf的内容宽度,0-592.28
 * @param {string} [param.filename='document.pdf'] - pdf文件名
 * @param {HTMLElement} param.header - 页眉dom元素
 * @param {HTMLElement} param.footer - 页脚dom元素
 */
export async function outputPDF({ element, contentWidth = 592,
  footer, header, filename = "测试A4分页.pdf" }) {
  if (!(element instanceof HTMLElement)) return
  // jsPDFs实例
  const _PDF = new jsPDF({ unit: 'pt', format: 'a4', orientation: 'p', });
  // 内容转换
  const { height: contentHeight, data: contentData } = await toCanvas(element, contentWidth);
  // 页脚元素 经过转换后在PDF页面的高度
  const { height: tfooterHeight } = await toCanvas(footer, contentWidth)
  // 页眉元素 经过转换后在PDF的高度
  const { height: theaderHeight } = await toCanvas(header, contentWidth);
  // 距离PDF左边的距离
  const baseX = (A4_WIDTH - contentWidth) / 2;        // 预留空间给左边
  // 出去页头、页眉、还有内容与两者之间的间距后 每页内容的实际高度
  const originalPageHeight = (A4_HEIGHT - tfooterHeight - theaderHeight);
  //获取分页符
  const pageBreaks = getPageBreaks(element, originalPageHeight, contentWidth, contentHeight);
  // 增加空白遮挡
  function addBlank(x, y, width, height) {
    y = Math.floor(y)
    _PDF.setFillColor(255, 255, 255);
    _PDF.rect(x, y, Math.ceil(width), Math.ceil(height), 'F');
  };

  // 添加页眉 
  async function addHeader() {
    const { height, data } = await toCanvas(header, A4_WIDTH);
    if (data) {
      _PDF.addImage(data, 'JPEG', 0, 0, A4_WIDTH, height);
    }
  }

  // 添加页脚
  async function addFooter(pageCount, current) {
    footer && footer.querySelector('.pdf-footer-page') && (footer.querySelector('.pdf-footer-page').innerText = current);
    footer && footer.querySelector('.pdf-footer-page-count') && (footer.querySelector('.pdf-footer-page-count').innerText = pageCount);
    const { height, data } = await toCanvas(footer, A4_WIDTH);
    if (data) {
      _PDF.addImage(data, 'JPEG', 0, A4_HEIGHT - height, A4_WIDTH, height)
    }
  }

  // 根据分页位置 开始分页
  for (let i = 0; i < pageBreaks.length; ++i) {
    console.log(`共${pageBreaks.length}页, 生成第${i + 1}页`)
    _PDF.addImage(contentData, 'JPEG', baseX, theaderHeight - pageBreaks[i], contentWidth, contentHeight);
    let blankH = pageBreaks[i + 1] ? (originalPageHeight - pageBreaks[i + 1] - pageBreaks[i] + tfooterHeight + 1) : 0
    addBlank(0, A4_HEIGHT - blankH, A4_WIDTH, blankH);
    addBlank(0, 0, A4_WIDTH, theaderHeight);
    await addHeader()
    await addFooter(pageBreaks.length, i + 1);
    if (i !== pageBreaks.length - 1) {
      _PDF.addPage();
    }
  }
  return _PDF.save(filename)
}
// 将元素转化为canvas元素
// 通过 放大 提高清晰度
// width为内容宽度
async function toCanvas(element, width) {
  if (!(element instanceof HTMLElement)) return { width: 0, height: 0, data: null };
  // canvas元素
  const canvas = await html2canvas(element, {
    scale: 4,  // 增加清晰度
    useCORS: true,// 允许跨域
    windowWidth: element.scrollWidth,
    windowHeight: element.scrollHeight,
    onrendered: function (canvas) {
      console.log(canvas);
      document.body.appendChild(canvas);
    }
  });
  // 高度转化为PDF的高度
  const height = (width / canvas.width) * canvas.height;
  // 转化成图片Data
  const canvasData = canvas.toDataURL('image/jpeg', 1.0);
  return { width, height, data: canvasData };
}
function getPageBreaks(element, originalPageHeight, contentWidth, contentHeight) {
  const pageBreaks = [0];
  // 获取元素距离网页顶部的距离
  // 通过遍历offsetParant获取距离顶端元素的高度值
  let rootElOffsetTop = 0
  function getElementTop(element) {
    let actualTop = element.offsetTop;
    let current = element.offsetParent;
    while (current && current !== null) {
      actualTop += current.offsetTop;
      current = current.offsetParent;
    }
    return actualTop - rootElOffsetTop;
  }
  rootElOffsetTop = getElementTop(element)
  // 元素在网页页面的宽度
  const elementWidth = element.offsetWidth;
  // PDF内容宽度 和 在HTML中宽度 的比, 用于将 元素在网页的高度 转化为 PDF内容内的高度, 将 元素距离网页顶部的高度  转化为 距离Canvas顶部的高度
  const rate = contentWidth / elementWidth
  // 遍历正常的元素节点
  function traversingNodes(nodes) {
    for (let i = 0; i < nodes.length; ++i) {
      const node = nodes[i];
      // 图片元素不需要继续深入,作为深度终点
      const isIMG = node.tagName === 'IMG';
      const isDivide = node.classList && node.classList.contains('divide');
      const isTableCol = node.tagName === 'TR';
      // 对需要处理分页的元素,计算是否跨界,若跨界,则直接将顶部位置作为分页位置,进行分页,且子元素不需要再进行判断
      let { offsetHeight } = node;
      // 计算出最终高度
      let offsetTop = getElementTop(node);
      // dom转换后距离顶部的高度
      // 转换成canvas高度
      const top = Math.floor(rate * (offsetTop))
      if (isTableCol || isIMG || isDivide || !(node.childNodes?.length > 0)) {
        // dom高度转换成生成pdf的实际高度
        // 代码不考虑dom定位、边距、边框等因素,需在dom里自行考虑,如将box-sizing设置为border-box
        updatePos(rate * offsetHeight, top, node);
      }
      // 对于普通元素,则判断是否高度超过分页值,并且深入
      else {
        // 遍历子节点
        traversingNodes(node.childNodes);
      }
    }
    return;
  }

  // 可能跨页元素位置更新的方法
  // 需要考虑分页元素,则需要考虑两种情况
  // 1. 普通达顶情况,如上
  // 2. 当前距离顶部高度加上元素自身高度 大于 整页高度,则需要载入一个分页点
  function updatePos(eheight, top) {
    // 如果高度已经超过当前页,则证明可以分页了
    if (top - (pageBreaks.length > 0 ? pageBreaks[pageBreaks.length - 1] : 0) >= originalPageHeight) {
      pageBreaks.push((pageBreaks.length > 0 ? pageBreaks[pageBreaks.length - 1] : 0) + originalPageHeight);
    }
    // 若 距离当前页顶部的高度 加上元素自身的高度 大于 一页内容的高度, 则证明元素跨页,将当前高度作为分页位置
    else if ((top + eheight - (pageBreaks.length > 0 ? pageBreaks[pageBreaks.length - 1] : 0) > originalPageHeight) && (top != (pageBreaks.length > 0 ? pageBreaks[pageBreaks.length - 1] : 0))) {
      pageBreaks.push(top);
    }
  }

  // 深度遍历节点的方法
  traversingNodes(element.childNodes);
  // 可能会存在遍历到底部元素为深度节点,可能存在最后一页位置未截取到的情况
  if (pageBreaks[pageBreaks.length - 1] + originalPageHeight < contentHeight) {
    pageBreaks.push(pageBreaks[pageBreaks.length - 1] + originalPageHeight);
  }
  return pageBreaks
}
示例 代码:

页眉页脚需使用行内样式

<div class="pdf-box">
    <div class="header" ref="pdfHeader" style="width: 778px;height: 88px;"></div>
         <div class="report" ref="pdfContenter">
         </div>
     <div class="footer" ref="pdfFooter" style="color: #000;font-size: 16px;text-align: center;line-height: 88px;width: 778px;height: 88px;">
         第
        <span class="pdf-footer-page"></span>
         页 / 共
         <span class="pdf-footer-page-count"></span>
         页
      </div>
</div>
import { outputPDF } from '@/utils/outputPDF'
let pdfContenter = ref(null) //获取dome 
let pdfHeader = ref(null)
let pdfFooter = ref(null)
outputPDF({
   element: pdfContenter.value,
   footer: pdfFooter.value,
   header: pdfHeader.value,
   filename: '作业报告.pdf'
})

首先,你需要将每个生成pdf保存为单独的文件,然后使用pdf-lib将它们合并为一个pdf文件。下面是一个基本的实现步骤: 1. 安装pdf-lib ```bash npm install pdf-lib ``` 2. 创建一个用于保存pdf文件的数组 ```javascript let pdfs = []; ``` 3. 使用jsPDFhtml2canvas组件生成pdf文件,并将其添加到pdfs数组中 ```javascript // 引入jsPDFhtml2canvas组件 import jsPDF from 'jspdf'; import html2canvas from 'html2canvas'; // 生成pdf文件 html2canvas(document.querySelector('#pdf-content')).then(canvas => { let pdf = new jsPDF('p', 'mm', 'a4'); pdf.addImage(canvas.toDataURL('image/png'), 'PNG', 0, 0, 211, 298); pdfs.push(pdf); }); ``` 4. 使用pdf-libpdfs数组中的所有pdf文件合并为一个pdf文件 ```javascript import { PDFDocument, StandardFonts } from 'pdf-lib'; // 将所有pdf文件合并为一个pdf文件 const mergedPdf = await PDFDocument.create(); const helveticaFont = await mergedPdf.embedFont(StandardFonts.Helvetica); for (let pdf of pdfs) { const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices()); copiedPages.forEach((page) => { mergedPdf.addPage(page); }); } mergedPdf.setTitle('Merged PDF'); mergedPdf.setAuthor('Your Name'); mergedPdf.setCreator('Your Organization'); const pdfBytes = await mergedPdf.save(); ``` 5. 下载合并后的pdf文件 ```javascript const blob = new Blob([pdfBytes], {type: 'application/pdf'}); const link = document.createElement('a'); link.href = window.URL.createObjectURL(blob); link.download = 'merged.pdf'; link.click(); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值