网页新增导出pdf功能实现方式

前端网页新增导出pdf功能实现方式

前言

本次遇到了这样的一个需求,给部署在vuepress上的文档新增导出功能,本质上就是一个把网页指定内容导出为pdf的需求。
vuepress本身只是一个支持md文档生成静态网页的框架式网站,尝试找过vuepress的插件,发现这方面的插件竟然没有支持网页端在线导出的。
在网上查阅大量资料后,发现许多方式需要编写服务来支持前端的导出pdf功能,而且纯前端实现导出功能,许多都会遇到样式上的问题。
通过半天对各种方式的尝试,对比后找到一种比较简单的方法能满足需求,这种方法使用浏览器自带功能,下面第二张图就是使用window.print()弹出打印页面,选择导出pdf后的效果。

在这里插入图片描述
最后导出可复制的pdf:
在这里插入图片描述

html2canvas+jsPDF

html2canvas 是一个用于将 HTML 元素转换为 Canvas 的库。它使用 HTML5 的 元素来捕获HTML 元素的内容。这对于将动态内容(如图表、SVG 等)转换为图像非常有用。

jsPDF 是一个用于生成 PDF 文件的库。它提供了许多方法来创建、编辑和保存 PDF 文件。当与 html2canvas 结合使用时,你可以先将 HTML 内容转换为 Canvas,然后使用 jsPDF 将 Canvas 内容导出为 PDF。

网上教程比较多的就是这种html2canvas+jsPDF的方法生成pdf,不过生成的pdf中文档的样式虽然保持住了,但是交互性为0。
这种方式本质上pdf中的文档都是以图片这种方式存在于文档中的,没办法进行复制。
这种适用只在乎布局样式,不适用于生成带交互性的文档。

具体实现如下:

//先引入js库
import html2canvas from 'html2canvas';
import jsPDF from 'jspdf';
	//因为是在vue组件里写的方法,有些语句需要自己改下
    async exportToPDF() {
      try {
        // 获取需要导出的元素,.theme-vdoing-wrapper为包含整个文档的class
        const element = document.querySelector(".theme-vdoing-wrapper ");
        let that = this;
        // 导出之前先将滚动条置顶,不然会出现数据不全的现象
        window.pageYOffset = 0
        document.documentElement.scrollTop = 0
        document.body.scrollTop = 0
        element.style.background = '#FFFFFF'
        html2canvas(element, {
          logging: false,
          dpi: window.devicePixelRatio * 4, // 将分辨率提高到特定的DPI 提高四倍
          scale: 2, // 按比例增加分辨率
          element: element,
          backgroundColor: '#ffffff',
          allowTaint: true,
          useCORS: true
        }).then(function (canvas) {
          var pdf = new jsPDF('p', 'mm', 'a4') // A4纸,纵向
          pdf.setFontSize(30) // 字体大小
          // pdf.text(20, 30, pdf)
          var ctx = canvas.getContext('2d')
          var a4w = 170; var a4h = 257 // A4大小,210mm x 297mm,四边各保留20mm的边距,显示区域170x257
          var imgHeight = Math.floor(a4h * canvas.width / a4w) // 按A4显示比例换算一页图像的像素高度
          var renderedHeight = 0
          var options = { pagesplit: true }

          while (renderedHeight < canvas.height) {
          var page = document.createElement('canvas')
           page.width = canvas.width
           page.height = Math.min(imgHeight, canvas.height - renderedHeight)// 可能内容不足一页

           // 用getImageData剪裁指定区域,并画到前面创建的canvas对象中
           page.getContext('2d').putImageData(ctx.getImageData(0, renderedHeight, canvas.width, Math.min(imgHeight, canvas.height - renderedHeight)), 0, 0)
           pdf.addImage(page.toDataURL('image/jpeg', 1.0), 'JPEG', 10, 10, a4w, Math.min(a4h, a4w * page.height / page.width)) // 添加图像到页面,保留10mm边距

           renderedHeight += imgHeight
           if (renderedHeight < canvas.height) { pdf.addPage() }// 如果后面还有内容,添加一个空页
          }
          var curDate = new Date().toLocaleString("zh-Hans-CN", { year: "numeric", month: "2-digit", day: "2-digit", hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" }).replace(/[\/ :]/g, "")
          pdf.save(that.exportName + curDate)
          that.isLoading = false;
        })
      } catch (error) {
        console.error('导出 PDF 失败:', error);
      }

    }

导出效果如下(不可复制):
在这里插入图片描述

html2pdf

这个是上面方法的简化版,本质上内部也是使用的html2canvas和jsPDF

具体实现:

import html2pdf from 'html2pdf.js';
   async exportPDF() {
      const element = document.querySelector(".theme-vdoing-wrapper ");
      const options = {
        margin: 1,
        filename: 'output.pdf',//输出文件名
        image: { type: 'jpeg', quality: 0.98 },
        html2canvas: { scale: 2, letterRendering: true, },
        jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' }
      };

      try {
        const pdf = await html2pdf().from(element).set(options).save();
      } catch (err) {
        console.error('Error exporting to PDF:', err);
      }
    },

导出效果如下(不可复制):
在这里插入图片描述

window.print()

这种方法是调用浏览器的打印功能,也支持导出pdf,浏览器会考虑网页的 CSS 样式来呈现打印内容,可以解决导出后样式不一致和文本不可复制的问题,成功解决了需求。

具体代码:

 printContent() {
      window.print();
 },

在这里插入图片描述
打印过程中想要隐藏元素,需要在css中添加如下内容:

/* 打印样式表 */
@media print {
    .hide-on-print {
        display: none; /* 隐藏不需要打印的元素 */
    }
    .article-list{
        display: none; /* 隐藏不需要打印的元素 */
    }
    .footer{
        display: none; /* 隐藏不需要打印的元素 */
    }
    .extra-class::before {
    display: none !important; /* 隐藏不需要打印的元素 */
  }
}

导出效果如下(可复制):
在这里插入图片描述

其他方式

pdfMake(不支持中文要自己装库)没有尝试成功。 PDFKit、Puppeteer、wkhtmltopdf、PhantomJS、Playwright都是在服务器端调用的。

其中尝试了Playwright,最终效果不太好,不支持指定内容导出pdf,导出的是整个网页的。
不过在这里还是附上步骤:

  • 先用express搭建一个服务,用于生成pdf

    npm init
    npm i express -D
    npm i playwright -D
    npm i cors -D
    
    //在工作目录下新建一个index.js
    const express = require('express');
    const playwright = require('playwright');
    const cors = require('cors');
    const app = express();
    app.use(express.json());
    app.use(cors())
    
    app.post('/api/generate-pdf', async (req, res) => {
      try {
        const browser = await playwright['chromium'].launch(); // 使用Chromium内核版本的Edge浏览器
        const context = await browser.newContext();
        const page = await context.newPage();
        const { reqUrl } = req.body;
        await page.goto(reqUrl , { waitUntil: 'domcontentloaded' }); // 确保页面加载完成
        // 生成 PDF
        const pdfOptions ={
          format: 'A4',
          printBackground: true,
          margin: {
            top: '1cm',
            right: '1cm',
            bottom: '1cm',
            left: '1cm',
          },
        };
         // 直接生成PDF并发送到前端
         const pdfBuffer = await page.pdf(pdfOptions);
         // 设置响应头,告知浏览器这是一个PDF文件
         res.set('Content-Type', 'application/pdf');
         res.set('Content-Disposition', `attachment;filename=generated.pdf`);
         // 发送PDF文件内容到前端
         res.send(pdfBuffer);
         // 关闭浏览器实例,释放资源
         await browser.close();
      } catch (error) {
        console.error('Error generating PDF:', error);
        res.status(500).send('Failed to generate PDF');
      }
    });
    app.listen(8082, () => {
        console.log(`Server is running on port 8082`);
      })
    
    #当前目录下启动服务
    node ./index.js
    
  • 前端新增方法通信
    核心方法:

	//一些配置可以自行百度搜索
    async exportToPdf() {
      try {
        const url = "http://localhost:8082/api/generate-pdf";
        const data = {
          /* 你的参数对象,比如:{htmlContent: 'your HTML content'} */
          reqUrl:'https://blog.csdn.net/qq_42888417/article/details/84317341'
        };
        // 使用fetch发起POST请求
        const response = await fetch(url, {
          method: "POST", // 或者 'PUT'
          headers: {
            "Content-Type": "application/json" // 假设后端期望接收JSON格式的数据
          },
          body: JSON.stringify(data) // 将数据转换为JSON字符串
        });
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        // 处理响应,直接下载PDF
        const blob = await response.blob(); // 获取blob数据
        const urlForBlob = window.URL.createObjectURL(blob);
        const link = document.createElement("a");
        link.href = urlForBlob;
        let now = new Date().toLocaleString('en-US', {year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit'}).replace(/[^\\d]/g, '')
        link.download = "redis"+now+".pdf";
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        window.URL.revokeObjectURL(urlForBlob); // 清除URL对象,避免内存泄漏
      } catch (error) {
        // 处理请求错误
        console.error("Error generating PDF:", error);
      }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值