前端实现DOM节点转PDF(分页内容连续版本)

1、解决了dom节点过长,pdf分页不连续问题

2、解决dom节点中存在视频,音频无法导出为pdf问题(大佬们有好的建议欢迎讨论)

话不多说 直接showCode

一、安装插件

import jsPDF from 'jspdf'

import html2canvas from 'html2canvas'

二、具体实现

版本一: 包含分页功能,导出的dom节点为普通节点(不包含视频,硬盘)

// import { set } from 'core-js/core/dict';
import html2Canvas from 'html2canvas';
import { jsPDF } from 'jspdf';

// pdfDom 页面 dom, intervalHeight 留白间距, fileName 文件名
export function html2Pdf(pdfDom, intervalHeight, fileName ,callback) {

    // 获取元素的高度
    function getElementHeight(element) {
        return element.offsetHeight;
    }
    // A4 纸宽高
    const A4_WIDTH = 592.28, A4_HEIGHT = 841.89;
    // 获取元素去除滚动条的高度
    const domScrollHeight = pdfDom.scrollHeight;
    const domScrollWidth = pdfDom.scrollWidth;
    // 保存当前页的已使用高度
    let currentPageHeight = 0;
    // 获取所有的元素 我这儿是手动给页面添加 class 用于计算高度 你也可以动态添加 这个不重要,主要是看逻辑
    let elements = pdfDom.querySelectorAll('.element');
    // 代表不可被分页
    let newPage = 'new-page';

    // 遍历所有内容的高度
    for (let element of elements) {
        let elementHeight = getElementHeight(element);
        // 检查添加这个元素后的总高度是否超过 A4 纸的高度
        if (currentPageHeight + elementHeight > A4_HEIGHT) {
            // 如果超过了,创建一个新的页面,并将这个元素添加到新的页面上
            currentPageHeight = elementHeight;
            element.classList.add(newPage);
        }
        currentPageHeight += elementHeight;
    }
    // 根据 A4 的宽高等比计算 dom 页面对应的高度
    const pageWidth = pdfDom.offsetWidth;
    const pageHeight = (pageWidth / A4_WIDTH) * A4_HEIGHT;
    // 将所有不允许被截断的子元素进行处理
    const wholeNodes = pdfDom.querySelectorAll(`.${newPage}`);
    // 插入空白块的总高度
    let allEmptyNodeHeight = 0;
    for (let i = 0; i < wholeNodes.length; i++) {
        // 判断当前的不可分页元素是否在两页显示
        const topPageNum = Math.ceil(wholeNodes[i].offsetTop / pageHeight);
        const bottomPageNum = Math.ceil((wholeNodes[i].offsetTop + wholeNodes[i].offsetHeight) / pageHeight);

        // 是否被截断
        if (topPageNum!== bottomPageNum) {
            // 创建间距
            const newBlock = document.createElement('div');
            newBlock.className = 'empty-node';
            newBlock.style.background = '#fff';
            // 计算空白块的高度,可以适当留出空间,根据自己需求而定
            const _H = topPageNum * pageHeight - wholeNodes[i].offsetTop;
            newBlock.style.height = _H + intervalHeight + 'px';
            // 插入空白块
            wholeNodes[i].parentNode.insertBefore(newBlock, wholeNodes[i]);
            // 更新插入空白块的总高度
            allEmptyNodeHeight = allEmptyNodeHeight + _H + intervalHeight;
        }
    }
    pdfDom.setAttribute(
        'style',
        `height: ${domScrollHeight + allEmptyNodeHeight}px; width: ${domScrollWidth}px;`,
    );
    console.log('%c [ pdfDom.offsetWidth ]-68', 'font-size:13px; background:pink; color:#bf2c9f;', pdfDom.offsetHeight,pdfDom.offsetWidth)

    return html2Canvas(pdfDom, {
        width: pdfDom.offsetWidth,
        height: pdfDom.offsetHeight,
        useCORS: true,
        allowTaint: true,
        scale: 3,
    }).then(canvas => {
        // dom 已经转换为 canvas 对象,可以将插入的空白块删除了
        const emptyNodes = pdfDom.querySelectorAll('.empty-node');
        for (let i = 0; i < emptyNodes.length; i++) {
            emptyNodes[i].style.height = 0;
            emptyNodes[i].parentNode.removeChild(emptyNodes[i]);
        }

        const canvasWidth = canvas.width, canvasHeight = canvas.height;
        // html 页面实际高度
        let htmlHeight = canvasHeight;
        // 页面偏移量
        let position = 0;

        // 根据 A4 的宽高等比计算 pdf 页面对应的高度
        const pageHeight = (canvasWidth / A4_WIDTH) * A4_HEIGHT;

        // html 页面生成的 canvas 在 pdf 中图片的宽高
        const imgWidth = A4_WIDTH;
        const imgHeight = 592.28 / canvasWidth * canvasHeight;
        // 将图片转为 base64 格式
        const imageData = canvas.toDataURL('image/jpeg', 1.0);

        // 生成 pdf 实例
        const PDF = new jsPDF('', 'pt', 'a4', true);

        // html 页面的实际高度小于生成 pdf 的页面高度时,即内容未超过 pdf 一页显示的范围,无需分页
        if (htmlHeight <= pageHeight) {
            PDF.addImage(imageData, 'JPEG', 0, 0, imgWidth, imgHeight);
        } else {
            while (htmlHeight > 0) {
                PDF.addImage(imageData, 'JPEG', 0, position, imgWidth, imgHeight);

                // 更新高度与偏移量
                htmlHeight -= pageHeight;
                position -= A4_HEIGHT;

                if (htmlHeight > 0) {
                    // 在 PDF 文档中添加新页面
                    PDF.addPage();
                }
            }
        }
        // 保存 pdf 文件
        PDF.save(`${fileName}.pdf`);  
            callback(null);
    }).catch(err => {
            callback(err);
    });
}

版本二: dom节点中包含视频,音频 需要转化成网址在展示

// import { set } from 'core-js/core/dict';
import html2Canvas from 'html2canvas';
import { jsPDF } from 'jspdf';

// pdfDom 页面 dom, intervalHeight 留白间距, fileName 文件名
export function html2Pdf(pdfDom, intervalHeight, fileName ,callback) {
  const hasVideo = hasVideoTagInElement(pdfDom);
  console.log('%c [ hasVideo ]-8', 'font-size:13px; background:pink; color:#bf2c9f;', hasVideo)
    // 获取元素的高度
    function getElementHeight(element) {
        return element.offsetHeight;
    }
    // A4 纸宽高
    const A4_WIDTH = 592.28, A4_HEIGHT = 841.89;
    // 获取元素去除滚动条的高度
    const domScrollHeight = pdfDom.scrollHeight;
    const domScrollWidth = pdfDom.scrollWidth;
    // 保存当前页的已使用高度
    let currentPageHeight = 0;
    // 获取所有的元素 我这儿是手动给页面添加 class 用于计算高度 你也可以动态添加 这个不重要,主要是看逻辑
    let elements = pdfDom.querySelectorAll('.element');
    // 代表不可被分页
    let newPage = 'new-page';

    // 遍历所有内容的高度
    for (let element of elements) {
        let elementHeight = getElementHeight(element);
        // 检查添加这个元素后的总高度是否超过 A4 纸的高度
        if (currentPageHeight + elementHeight > A4_HEIGHT) {
            // 如果超过了,创建一个新的页面,并将这个元素添加到新的页面上
            currentPageHeight = elementHeight;
            element.classList.add(newPage);
        }
        currentPageHeight += elementHeight;
    }
    // 根据 A4 的宽高等比计算 dom 页面对应的高度
    const pageWidth = pdfDom.offsetWidth;
    const pageHeight = (pageWidth / A4_WIDTH) * A4_HEIGHT;
    // 将所有不允许被截断的子元素进行处理
    const wholeNodes = pdfDom.querySelectorAll(`.${newPage}`);
    // 插入空白块的总高度
    let allEmptyNodeHeight = 0;
    for (let i = 0; i < wholeNodes.length; i++) {
        // 判断当前的不可分页元素是否在两页显示
        const topPageNum = Math.ceil(wholeNodes[i].offsetTop / pageHeight);
        const bottomPageNum = Math.ceil((wholeNodes[i].offsetTop + wholeNodes[i].offsetHeight) / pageHeight);

        // 是否被截断
        if (topPageNum!== bottomPageNum) {
            // 创建间距
            const newBlock = document.createElement('div');
            newBlock.className = 'empty-node';
            newBlock.style.background = '#fff';
            // 计算空白块的高度,可以适当留出空间,根据自己需求而定
            const _H = topPageNum * pageHeight - wholeNodes[i].offsetTop;
            newBlock.style.height = _H + intervalHeight + 'px';
            // 插入空白块
            wholeNodes[i].parentNode.insertBefore(newBlock, wholeNodes[i]);
            // 更新插入空白块的总高度
            allEmptyNodeHeight = allEmptyNodeHeight + _H + intervalHeight;
        }
    }
    pdfDom.setAttribute(
        'style',
        `height: ${domScrollHeight + allEmptyNodeHeight}px; width: ${domScrollWidth}px;`,
    );

    return html2Canvas(pdfDom, {
        width: pdfDom.offsetWidth,
        height: pdfDom.offsetHeight,
        useCORS: true,
        allowTaint: true,
        scale: 3,
    }).then(canvas => {
        // dom 已经转换为 canvas 对象,可以将插入的空白块删除了
        const emptyNodes = pdfDom.querySelectorAll('.empty-node');
        for (let i = 0; i < emptyNodes.length; i++) {
            emptyNodes[i].style.height = 0;
            emptyNodes[i].parentNode.removeChild(emptyNodes[i]);
        }

        const canvasWidth = canvas.width, canvasHeight = canvas.height;
        // html 页面实际高度
        let htmlHeight = canvasHeight;
        // 页面偏移量
        let position = 0;

        // 根据 A4 的宽高等比计算 pdf 页面对应的高度
        const pageHeight = (canvasWidth / A4_WIDTH) * A4_HEIGHT;

        // html 页面生成的 canvas 在 pdf 中图片的宽高
        const imgWidth = A4_WIDTH;
        const imgHeight = 592.28 / canvasWidth * canvasHeight;
        // 将图片转为 base64 格式
        const imageData = canvas.toDataURL('image/jpeg', 1.0);

        // 生成 pdf 实例
        const PDF = new jsPDF('', 'pt', 'a4', true);

        // html 页面的实际高度小于生成 pdf 的页面高度时,即内容未超过 pdf 一页显示的范围,无需分页
        if (htmlHeight <= pageHeight) {
            PDF.addImage(imageData, 'JPEG', 0, 0, imgWidth, imgHeight);
        } else {
            while (htmlHeight > 0) {
                PDF.addImage(imageData, 'JPEG', 0, position, imgWidth, imgHeight);

                // 更新高度与偏移量
                htmlHeight -= pageHeight;
                position -= A4_HEIGHT;

                if (htmlHeight > 0) {
                    // 在 PDF 文档中添加新页面
                    PDF.addPage();
                }
            }
        }
        // 保存 pdf 文件
        PDF.save(`${fileName}.pdf`);  
            callback(null);
    }).catch(err => {
            callback(err);
    });
}
function hasVideoTagInElement(element) {
    if (element.tagName && element.tagName.toLowerCase() === 'video') {
        const newP = document.createElement('div');
        newP.textContent = element.src;
        element.parentNode.replaceChild(newP, element);
        return true;
    }
    if (element.tagName && element.tagName.toLowerCase() === 'audio') {
        const newP = document.createElement('div1');
        newP.textContent = element.src;
        element.parentNode.replaceChild(newP, element);
       
    }
    let children = element.childNodes;
    for (let i = 0; i < children.length; i++) {
        let child = children[i];
        if (child.nodeType === 1 && hasVideoTagInElement(child)) {
            return true;
        }
    }
    return false;
}

具体使用:

import { html2Pdf } from '@/utils/htmlToPdf'

注意  使用refs引用 或者id
    exportReportFile() {
      html2Pdf(this.$refs.editorHtml, 20, `任务:${this.baseData.taskName}检测报告`, (err) => {
        if (err) {
          this.$message.error("生成 PDF 失败!");
        } else {
          this.$message.success("生成 PDF 成功!");
        }
        if (this.baseData.modalType == 'video' || this.baseData.modalType == 'audio') {
          location.reload();
        }
      })

    },

分页数据高度可根据业务调整,分辨率问题 调整: scale参数到合适的为止

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值