• Vue - 将页面内容下载为 pdf 格式文件(html2canvas + jspdf)


    一. 安装所需依赖

    npm install html2canvas --save
    npm install jspdf --save

    二. htmlToPdfNew.js 核心文件

    /* eslint-disable */
    import jsPDF from "jspdf";
    import html2canvas from "html2canvas";
    
    /*
     * 使用说明
     * ele:需要导出pdf的容器元素(dom节点 不是id)
     * pdfFileName: 导出文件的名字 通过调用outPutPdfFn方法也可传参数改变
     * splitClassName: 避免分段截断的类名 当pdf有多页时需要传入此参数 , 避免pdf分页时截断元素  如表格
     * 调用方式 先 let pdf = new PdfLoader(ele, 'pdf' ,'itemClass');再 pdf.outPutPdfFn()
     * 若想改变pdf名称 pdf.outPutPdfFn(fileName);  outPutPdfFn方法返回一个promise 可以使用then方法处理pdf生成后的逻辑
     * */
    class PdfLoader {
      constructor(ele, pdfFileName, splitClassName) {
        this.ele = ele;
        this.pdfFileName = pdfFileName;
        this.splitClassName = splitClassName;
        this.A4_WIDTH = 595;
        this.A4_HEIGHT = 842;
      }
    
      async getPDF() {
        // debugger
        let ele = this.ele;
        let pdfFileName = this.pdfFileName;
        let eleW = ele.offsetWidth; // 获得该容器的宽
        let eleH = ele.scrollHeight; // 获得该容器的高
        let eleOffsetTop = ele.offsetTop; // 获得该容器到文档顶部的距离
        let eleOffsetLeft = ele.offsetLeft; // 获得该容器到文档最左的距离
        let canvas = document.createElement("canvas");
        let abs = 0;
        let win_in =
          document.documentElement.clientWidth || document.body.clientWidth; // 获得当前可视窗口的宽度(不包含滚动条)
        let win_out = window.innerWidth; // 获得当前窗口的宽度(包含滚动条)
        if (win_out > win_in) {
          abs = (win_out - win_in) / 2; // 获得滚动条宽度的一半
        }
        canvas.width = eleW * 2; // 将画布宽&&高放大两倍
        canvas.height = eleH * 2;
        let context = canvas.getContext("2d");
        context.scale(3, 3); // 增强图片清晰度
        context.translate(-eleOffsetLeft - abs, -eleOffsetTop);
        html2canvas(ele, {
          useCORS: true, //允许canvas画布内可以跨域请求外部链接图片, 允许跨域请求。
        }).then(async (canvas) => {
          // debugger
          let contentWidth = canvas.width;
          let contentHeight = canvas.height;
          //一页pdf显示html页面生成的canvas高度;
          let pageHeight = (contentWidth / this.A4_WIDTH) * this.A4_HEIGHT; // 这样写的目的在于保持宽高比例一致 pageHeight/canvas.width = a4纸高度/a4纸宽度// 宽度和canvas.width保持一致
          //未生成pdf的html页面高度
          let leftHeight = contentHeight;
          //页面偏移
          let position = 30;
          //a4纸的尺寸[595,842],单位像素,html页面生成的canvas在pdf中图片的宽高
          let imgWidth = this.A4_WIDTH - 60; //-60为了页面有左右边距  是一边30
          let imgHeight = (this.A4_WIDTH / contentWidth) * contentHeight;
          let pageData = canvas.toDataURL("image/jpeg", 1.0);
          let pdf = jsPDF("", "pt", "a4");
          //有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
          //当内容未超过pdf一页显示的范围,无需分页
          if (leftHeight < pageHeight) {
            //在pdf.addImage(pageData, 'JPEG', 左20,上,宽度,高度)设置在pdf中显示;
            pdf.addImage(pageData, "JPEG", 30, 20, imgWidth, imgHeight);
            // pdf.addImage(pageData, 'JPEG', 20, 40, imgWidth, imgHeight);
          } else {
            // 分页
            while (leftHeight > 0) {
              pdf.addImage(pageData, "JPEG", 30, position, imgWidth, imgHeight);
              leftHeight -= pageHeight;
              position -= this.A4_HEIGHT;
              //避免添加空白页
              if (leftHeight > 0) {
                pdf.addPage();
              }
            }
          }
          pdf.save(pdfFileName + ".pdf", { returnPromise: true }).then(() => {
            //去除添加的空div 防止页面混乱
            let doms = document.querySelectorAll(".emptyDiv");
            for (let i = 0; i < doms.length; i++) {
              doms[i].remove();
            }
          });
          this.ele.style.height = "";
        });
      }
      isSplit(nodes, index, pageHeight) {
        if (
          nodes[index].offsetTop + nodes[index].offsetHeight < pageHeight &&
          nodes[index + 1] &&
          nodes[index + 1].offsetTop + nodes[index + 1].offsetHeight > pageHeight
        ) {
          return true;
        }
        return false;
      }
      async outPutPdfFn(pdfFileName) {
        // debugger
        return new Promise((resolve, reject) => {
          this.ele.style.height = "initial";
          pdfFileName ? (this.pdfFileName = pdfFileName) : null;
          let target = this.ele;
          let pageHeight = (target.scrollWidth / this.A4_WIDTH) * this.A4_HEIGHT;
          // 获取分割dom,此处为class类名为item的dom
          let domList = document.getElementsByClassName(this.splitClassName);
          // 进行分割操作,当dom内容已超出a4的高度,则将该dom前插入一个空dom,把他挤下去,分割
          let pageNum = 1; //pdf页数
          let eleBounding = this.ele.getBoundingClientRect();
          for (let i = 0; i < domList.length; i++) {
            let node = domList[i];
            let bound = node.getBoundingClientRect();
            let offset2Ele = bound.top - eleBounding.top;
            let currentPage = Math.ceil(
              (bound.bottom - eleBounding.top) / pageHeight
            ); //当前元素应该在哪一页
            if (pageNum < currentPage) {
              pageNum++;
              let divParent = domList[i].parentNode; // 获取该div的父节点
              let newNode = document.createElement("div");
              newNode.className = "emptyDiv";
              newNode.style.background = "white";
              newNode.style.height =
                pageHeight * (pageNum - 1) - offset2Ele + 30 + "px"; //+30为了在换下一页时有顶部的边距
              newNode.style.width = "100%";
              let next = domList[i].nextSibling; // 获取div的下一个兄弟节点
              // 判断兄弟节点是否存在
              if (next) {
                // 存在则将新节点插入到div的下一个兄弟节点之前,即div之后
                divParent.insertBefore(newNode, node);
              } else {
                // 不存在则直接添加到最后,appendChild默认添加到divParent的最后
                divParent.appendChild(newNode);
              }
            }
          }
          // 异步函数  再调用保存为pdf的函数
          // this.getPDF(resolve, reject)
        });
      }
    }
    
    export default PdfLoader;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143

    三. 组件使用方法

    1. 引入 htmlToPdfNew.js 核心文件
      import PdfLoader from "./htmlToPdfNew.js";
      
      • 1
    2. dom 设置
      
      <button @click="downLoadPdf">下载button>
      
      <div id="pdfContainer">div>
      
      • 1
      • 2
      • 3
      • 4
    3. 下载事件方法
      downLoadPdf() {
        let ele = document.getElementById("pdfContainer");
        // pdf.outPutPdfFn(this.userName + '分析报告')
        let pdf = new PdfLoader(ele, "测试", "itemClass");
        pdf.getPDF();
      },
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

    四. 组件使用实例

    <template>
      <div class="home">
        
        <button @click="downLoadPdf">下载button>
        
        <div id="pdfContainer" class="pdfContainer">
          <div class="title">PDF 标题div>
          <div class="itemClass">div>
          <div class="info">
            <div class="text1">君不见黄河之水天上来,奔流到海不复回。div>
            <div class="text2">君不见高堂明镜悲白发,朝如青丝暮成雪。div>
          div>
        div>
      div>
    template>
    
    <script>
    import PdfLoader from "./htmlToPdfNew.js";
    export default {
      methods: {
        downLoadPdf() {
          let ele = document.getElementById("pdfContainer");
          // pdf.outPutPdfFn(this.userName + '分析报告')
          let pdf = new PdfLoader(ele, "测试", "itemClass");
          pdf.getPDF();
        },
      },
    };
    script>
    
    <style scoped lang="scss">
    .home {
      width: 100%;
      height: 100%;
      .pdfContainer {
        width: 800px;
        padding: 10px 20px;
        border: 1px solid #e2e2e2;
        box-sizing: border-box;
        text-align: center;
        .title {
          text-align: center;
          font-weight: bold;
          padding-bottom: 10px;
        }
        .info {
          line-height: 35px;
          .text1 {
            color: rgb(0, 170, 255);
          }
          .text2 {
            color: rgb(255, 119, 0);
          }
        }
      }
    }
    style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
  • 相关阅读:
    Python的高级用法:命名元组
    SSM+中小型企业绩效管理系统 毕业设计-附源码081536
    数智竞技何以成为“科技+体育”新样本?
    最频繁被问到的SQL面试题
    Solidity拓展:数学运算过程中数据长度溢出的问题
    docker概念、安装与卸载
    Java 18 还未用上,最新Java 19 则出来了
    Set 集合和其之类 HashSet、LinkedHashSet
    操作系统 内存对齐
    python 军棋小游戏代码
  • 原文地址:https://blog.csdn.net/Jie_1997/article/details/132905924