纯前端生成PDF(jsPDF)并下载保存或上传到OSS

前言

在工作中遇到了一个需求,就是把前端页面生成PDF并保存在本地,因为前端网站可能会展示各种表格,图表信息内容并带有比较鲜艳的色彩样式,如果让后端生产的PDF的话样式可能和前端页面展示的有所差异,所以这个任务就落到了前端的身上。

技术涉及

  • jsPDF

  • html2canvas

  • ali-oss

代码实现

1、获取DOM结点

首先需要获取需要打印的DOM结点,这个时候获取的DOM结点是带有样式的,就相当于页面中的内容

javascript 复制代码
 const eleHtml = document.querySelector('.zxksBody');

2、获取打印容器的属性

首先做个兼容判断,判断是否取到了DOM结点信息,如果取到了DOM结点就获取DOM结点的内容,进行高度和宽度的赋值

javascript 复制代码
 if (eleHtml) {
    let eleW = eleHtml.offsetWidth; // 获得该容器的宽
    let eleH = eleHtml.offsetHeight; // 获得该容器的高
 }

3、生成PDF

这一步就是把获取到的DOM结点,通过jsPDF和html2canvas生成为PDF

javascript 复制代码
html2canvas(eleHtml, {
        dpi: 300,
        width: eleW,
        height: eleH,
        scale: 2, // 提高渲染质量
        useCORS: true  //允许canvas画布内 可以跨域请求外部链接图片, 允许跨域请求。
      }).then(async (canvas) => {
            const pdf = new jsPDF('', 'pt', 'a4');
            const imgData = canvas.toDataURL('image/png', 1.0);
            //a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
            const imgWidth = 555.28;
            //一页pdf显示html页面生成的canvas高度;
            const imgHeight = 555.28 / canvas.width * canvas.height;
            // 计算分页
            const pageHeight = 841.89;
            //未生成pdf的html页面高度
            let leftHeight = imgHeight;
            //页面偏移
            let position = 0;

            if (leftHeight < pageHeight) {
              //在pdf.addImage(pageData, 'JPEG', 左,上,宽度,高度)设置在pdf中显示
              pdf.addImage(imgData, 'PNG', 20, 20, imgWidth, imgHeight);
            } else { // 分页
               while (leftHeight > 0) {
                  pdf.addImage(imgData, 'PNG', 20, position, imgWidth, imgHeight);
                  leftHeight -= pageHeight;
                  position -= 841.89;
                  if (leftHeight > 0) {
                      pdf.addPage();
                  }
               }
         });

4、保存本地或者上传OSS

保存本地

保存本地的话比较简单,直接调用PDF库自带的方法就可以保存到本地

javascript 复制代码
pdf.save(`${state.xsMc}-${state.xsBh}.pdf`)
上传OSS

上传的OSS的话就比较复杂一点,首先就是需要配置OSS的内容,然后把PDF转换为Blob对象,最后就是调用OSS的接口实现上传。

javascript 复制代码
// 配置OSS
const client = new OSS({
  region: "******",
  bucket: 'bucketName',
  endpoint: 'endpoint',
  stsToken: 'securityToken',
  accessKeyId: 'accessKeyId',
  accessKeySecret: 'accessKeySecret',
});


// 将 PDF 文件转换为 Blob 对象
const pdfBlob = pdf.output('blob');

// 调用OSS上方实现上传
const fileRes = await client.put(`${state.xsMc}-${state.xsBh}.pdf`, pdfBlob);
console.log(fileRes, '接收返回的OSS信息');

5、注意事项

  • 使用html2canvas和jsPDF可能会遇见文本错位或者样式错误问题,这个时候需要进行调整,可以通过html2canvas中的onclone回调方法进行调整
javascript 复制代码
html2canvas(eleHtml, {
  onclone: (documentClone) => {
    // 在克隆的文档上进行修改
    const partRight2 = documentClone.querySelector('.partRight2');
    const titleBars = documentClone.querySelectorAll('.titleBar');

    if (partRight2) {
      partRight2.style.display = 'none'; // 隐藏内容
    }
    if (titleBars) {
      //修改样式属性
      titleBars.forEach(titleBar => {
        titleBar.style.marginTop = '-8px';
        titleBar.style.marginBottom = '20px';
      });
    }
  },
  dpi: 300,
  width: eleW,
  height: eleH,
  scale: 2, // 提高渲染质量
  useCORS: true  //允许canvas画布内 可以跨域请求外部链接图片, 允许跨域请求。
}).then(async (canvas) => {
        .......
    });
  • 对于在获取DOM时,带有滚动条的内容无法正确获取他的高度和宽度,内容可能会被遮盖无法正确打印,这个时候需要在打印前更改页面中的DOM样式才能正确打印
javascript 复制代码
// 获取全部内容
const eleHtml = document.querySelector('.zxksBody');

// 在生成canvas之前就把样式进行更改,获取盒子的正常高度或者宽度,防止样式被遮盖,
const changeHeight = document.querySelector('.zxksContent');

if (changeHeight) {
  changeHeight.style.height = '100%'; // 更改高度
}

html2canvas(eleHtml, {
    dpi: 300,
    width: eleW,
    height: eleH,
    scale: 2, // 提高渲染质量
    useCORS: true  //允许canvas画布内 
 }).then(async (canvas) => {

        .....

        // 在打印完成后,再把样式改回去
        if (changeHeight) {
           changeHeight.style.height = 'calc(100vh - 182px)';
        }
    }
  • 对于带有滚动条的div盒子,在点击打印时,最好把页面内容进行更改,防止无法正确获取盒子高度,导致文字被隐藏,在打印完成后,在更改回去

// 对于vue

可以使用v-if进行更换,把展示的内容保存在div中,去掉溢出滚动功能

// 对于react

可以使用三元运算符进行判断,展示的内容

6、完整代码

javascript 复制代码
const printPdf = async () => {
       const client = new OSS({
              const client = new OSS({
              region: "******",
              bucket: 'bucketName',
              endpoint: 'endpoint',
              stsToken: 'securityToken',
              accessKeyId: 'accessKeyId',
              accessKeySecret: 'accessKeySecret',
        }); 
       try {
          // 获取全部内容
          const eleHtml = document.querySelector('.zxksBody');
          // 带有移除隐藏的功能
          const changeHeight = document.querySelector('.zxksContent');

          if (changeHeight) {
            changeHeight.style.height = '100%'; // 更改高度
          }

          if (eleHtml) {
            let eleW = eleHtml.offsetWidth; // 获得该容器的宽
            let eleH = eleHtml.offsetHeight; // 获得该容器的高

            // 确保获取加载完全的DOM
            setTimeout(() => { 
              html2canvas(eleHtml, {
                onclone: (documentClone) => {
                  // 在克隆的文档上进行修改
                  const partRight2 = documentClone.querySelector('.partRight2');
                  const titleBars = documentClone.querySelectorAll('.titleBar');

                  if (partRight2) {
                    partRight2.style.display = 'none'; // 隐藏内容
                  }
                  if (titleBars) {
                    titleBars.forEach(titleBar => {
                      titleBar.style.marginTop = '-8px';
                      titleBar.style.marginBottom = '20px';
                    });
                  }
                },
                dpi: 300,
                width: eleW,
                height: eleH,
                scale: 2, // 提高渲染质量
                useCORS: true  //允许canvas画布内 可以跨域请求外部链接图片, 允许跨域请求。
              }).then(async (canvas) => {
                const pdf = new jsPDF('', 'pt', 'a4');
                const imgData = canvas.toDataURL('image/png', 1.0);
                const imgWidth = 555.28;
                const imgHeight = 555.28 / canvas.width * canvas.height;

                // 计算分页
                const pageHeight = 841.89;
                let leftHeight = imgHeight;
                let position = 0;

                if (leftHeight < pageHeight) {
                  pdf.addImage(imgData, 'PNG', 20, 20, imgWidth, imgHeight);
                } else {
                  while (leftHeight > 0) {
                    pdf.addImage(imgData, 'PNG', 20, position, imgWidth, imgHeight);
                    leftHeight -= pageHeight;
                    position -= 841.89;
                    if (leftHeight > 0) {
                      pdf.addPage();
                    }
                  }
                }

                // 将 PDF 文件转换为 Blob 对象
                const pdfBlob = pdf.output('blob');
              
                // 使用 OSS 客户端上传 Blob 对象
                try {
                  const fileRes = await client.put(`${state.xsMc}-${statexsBh}.pdf`, pdfBlob);
                  console.log('client res', fileRes);
                } catch (err) {
                  console.error('PDF上传失败,请重新提交!', err);
                }

                if (changeHeight) {
                  changeHeight.style.height = 'calc(100vh - 182px)';
                }
              });
            }, 1000);
          }
        } catch (error) {
          console.log("Error!", error);
          if (changeHeight) {
            changeHeight.style.height = 'calc(100vh - 182px)';
          }
        }
      };
相关推荐
我曾经是个程序员26 分钟前
鸿蒙学习记录
开发语言·前端·javascript
羊小猪~~40 分钟前
前端入门之VUE--ajax、vuex、router,最后的前端总结
前端·javascript·css·vue.js·vscode·ajax·html5
摸鱼了1 小时前
🚀 从零开始搭建 Vue 3+Vite+TypeScript+Pinia+Vue Router+SCSS+StyleLint+CommitLint+...项目
前端·vue.js
程序员shen1616111 小时前
抖音短视频saas矩阵源码系统开发所需掌握的技术
java·前端·数据库·python·算法
Ling_suu2 小时前
SpringBoot3——Web开发
java·服务器·前端
Yvemil72 小时前
《开启微服务之旅:Spring Boot Web开发》(二)
前端·spring boot·微服务
hanglove_lucky2 小时前
本地摄像头视频流在html中打开
前端·后端·html
维李设论2 小时前
Node.js的Web服务在Nacos中的实践
前端·spring cloud·微服务·eureka·nacos·node.js·express
2401_857600952 小时前
基于 SSM 框架 Vue 电脑测评系统:赋能电脑品质鉴定
前端·javascript·vue.js
天之涯上上2 小时前
Pinia 是一个专为 Vue.js 3 设计的状态管理库
前端·javascript·vue.js