html2canvas,jsPDF 导出内容和表格截断的问题解决

237 阅读4分钟

这周公司提了一个需求,就是一键导出几个 tab 页pdf,在网上查找了不少资料,关于使用html2canvas,jsPDF导出的内容截断的帖子不少,但大多都是最基础的导出,即便有相关的内容截断的帖子,大多也不太行... 所以我就在网上找了几个代码片段杂糅在一起,然后稍微改了改

其实内容和表格的处理思路是差不多的,需要给有截断风险的dom元素添加上特定的class,计算出最后一个不超过 A4 高度的元素,其底部到 A4 底部的高度进行遮白处理,其下一个元素要增加到下一页,这就是内容截断的处理思路

downloadPdf() {
      console.log("printing");
      const first = document.getElementById("first");
      const second = document.getElementById("second");
      const three = document.getElementById("three");
      const four = document.getElementById("four");
      const five = document.getElementById("five");
      // let list = [first, second, three];
      let list = [
        {
          name: "first",
          label:'tab1',
          dom: first,
        },
        {
          name: "four",
          label:'tab2',
          dom: four,
        },
        {
          name: "second",
          label:'tab3',
          dom: second,
        },
        {
          name: "three",
          label:'tab4',
          dom: three,
        },

        {
          name: "five",
          label:'tab5',
          dom: five,
        },
      ];
      // 使用递归逐个生成 PDF
      this.generatePDFRecursively(list, 0, new JSZip());
    },
generatePDFRecursively(list, index, zip) {
      if (index >= list.length) {
        console.log("All PDFs generated");
        // 生成 ZIP 文件并下载
        // 生成 ZIP 文件并下载
        zip.generateAsync({ type: "blob" }).then((content) =>{
          saveAs(content, `${this.data.answer['姓名']}简历.zip`);
        });
        return;
      }

      const htmlContent = list[index].dom;
      this.activeName = list[index].name;
      this.$nextTick(async () => {
        const pdfBlob = await this.generatePDF(htmlContent);
        zip.file(`${this.data.answer['姓名']}${list[index].label}.pdf`, pdfBlob);

        // 递归处理下一个元素
        this.generatePDFRecursively(list, index + 1, zip);
      });
    },
    async generatePDF(htmlContent) {
      //A4纸张的宽度和高度
      const A4_WIDTH = 592.28;

      const A4_HEIGHT = 841.89;

      //获取DOM节点,这个节点包含的是你想要导出的PDF内容

      const printDom = htmlContent;

      //计算页面的高度

      const pageHeight =
        (printDom.getBoundingClientRect().width / A4_WIDTH) * A4_HEIGHT;

      //获取DOM节点,是可能会被截断的DOM节点

      const wholeNodes = document.querySelectorAll(".whole-node");
      for (let i = 0; i < wholeNodes.length; i++) {
        let headNum = 0; // 当前元素的上边界所在的页码
        let tailNum = 0; // 当前元素的下边界所在的页码
        if (wholeNodes[i].nodeName == "TR") {
          // 针对表格特殊处理
          var startPos = printDom.getBoundingClientRect().top; // 获取当前pdf内容相对于当前页面窗口的偏移量
          var nowPos = wholeNodes[i].getBoundingClientRect().top; // 当前元素的偏移量
          // nowPos - startPos表示 当前元素的上边界 距离 pdf内容的上边界 的高度
          headNum = Math.ceil((nowPos - startPos) / pageHeight);
          tailNum = Math.ceil(
            (nowPos - startPos + wholeNodes[i].offsetHeight) / pageHeight
          );
          if (headNum !== tailNum) {
            // 页码不一致,说明该元素会被截断
            // 2、计算插入空白块的高度
            let _H = headNum * pageHeight - (nowPos - startPos);

            // 3、插入空白块使被截断元素下移
            let divParent = wholeNodes[i].parentNode;
            let newNode = document.createElement("div");
            newNode.className = "emptyDiv";
            newNode.style.background = "#ffffff";
            newNode.style.height = _H + 10 + "px"; // 适当留出一点距离
            divParent.insertBefore(newNode, wholeNodes[i]);
          }
        } else {
          //计算分页的页数
          const bottomPageNum = Math.ceil(
            (wholeNodes[i].offsetTop + wholeNodes[i].offsetHeight) / pageHeight
          );
          //这个条件判断是元素i距离上方或上层控件的位置+元素i本身的高度小于A4纸的高度,并且下一个元素距离上方或上层控件的位置+下一个元素本身的高度大于A4纸的高度意味着当前页面的内容则在中间插入一个空白块,空白的高度计算为:A4纸的高度减去减去元素i的offsetTop+offsetHeight需要分页处理。
          if (
            wholeNodes[i].offsetTop + wholeNodes[i].offsetHeight <
              pageHeight * bottomPageNum &&
            wholeNodes[i + 1] &&
            wholeNodes[i + 1].offsetTop + wholeNodes[i + 1].offsetHeight >
              pageHeight * bottomPageNum
          ) {
            const divParent = wholeNodes[i].parentNode;

            const newBlock = document.createElement("div");

            newBlock.className = "emptyDiv";

            newBlock.style.background = "#fff";

            //计算空白区域的高度,以便正确地填充在当前页面和下一页面之间。_H 是当前节点到底部的距离,32px 是一个额外的空间,作为安全边距或分页缓冲

            const _H =
              bottomPageNum * pageHeight -
              wholeNodes[i].offsetTop -
              wholeNodes[i].offsetHeight;

            newBlock.style.height = `${_H + 32}px`;

            const next = wholeNodes[i].nextSibling;

            if (next) {
              divParent.insertBefore(newBlock, wholeNodes[i]);
            } else {
              divParent.appendChild(newBlock);
            }
          }
        }
      }

      //使用 html2canvas 库将 printDom 元素的内容渲染成一个图像(canvas)
      const canvas = await html2canvas(printDom, {
        useCORS: true,

        scale: 2,
      });

      //使用 html2canvas 再次渲染 printDom,并在渲染完成后执行 then() 回调,删除刚刚创建的空白占位元素。
      html2canvas(printDom, {
        height: printDom.getBoundingClientRect().height,

        width: printDom.getBoundingClientRect().width,

        allowTaint: true,
      }).then(() => {
        const emptyDivs = document.querySelectorAll(".emptyDiv");

        for (let i = 0; i < emptyDivs.length; i++) {
          emptyDivs[i].parentNode.removeChild(emptyDivs[i]);
        }
      });
      const contentWidth = parseInt(canvas.style.width) * 2;

      const contentHeight = parseInt(canvas.style.height) * 2;

      const imgWidth = 592.28;

      const imgHeight = (592.28 / contentWidth) * contentHeight;

      const pageData = canvas.toDataURL("image/jpeg", 1.0);

      const PDF = new jsPDF("p", "pt", "a4");

      let leftHeight = contentHeight;

      //初始化 leftHeight 为内容的总高度,position 用于控制每一页图像的位置。
      let position = 0;

      //判断 leftHeight 是否小于等于页面的高度 pageHeight。如果小于等于,直接将图像添加到 PDF 中。如果大于,则分多页添加图像,每页添加 841.89 的高度,直到所有内容都被添加到 PDF 中。position 用于控制图像在每页中的垂直位置。
      if (leftHeight <= pageHeight) {
        PDF.addImage(pageData, "JPEG", 0, 0, imgWidth, imgHeight);
      } else {
        while (leftHeight > 0) {
          PDF.addImage(pageData, "JPEG", 0, position, imgWidth, imgHeight);

          leftHeight -= (contentWidth / 592.28) * 841.89;

          position -= 841.89;

          if (leftHeight > 0) {
            PDF.addPage();
          }
        }
      }

      return PDF.output('blob');
    }