Skip to content

前端pdf预览方案

Published:

该方案基于react + vite

如果我们要在项目中实现 pdf 预览的效果。一共有三种方案进行选择

下面将展示三种方法的具体使用。

PDF 嵌入

此方案需要浏览器原生支持 PDF 预览。截至 2024 年,大多数移动设备都不支持此方案。主要有两种实现方式:

html 标签

可使用 iframeembedobject 标签引用 PDF 文件。但此方法兼容性较差。

示例代码:

import React from "react";
import PdfUrl from "/path/to/file.pdf";

// 生成一个指向本地 PDF 文件的绝对 URL
const pdfUrl = new URL(PdfUrl, import.meta.url).href;

export default () => {
  return (
    <>
      <iframe src={pdfUrl} width="100%" height="500px">
        <p>
          抱歉,您的浏览器不支持加载 PDF 文件。您可以
          <a href={pdfUrl}>点击这里</a> 下载文件。
        </p>
      </iframe>

      <embed
        src={pdfUrl}
        type="application/pdf"
        width="100%"
        height="500px"
      ></embed>

      <object data={pdfUrl} type="application/pdf" width="100%" height="500px">
        <p>
          抱歉,您的浏览器不支持加载 PDF 文件。您可以
          <a href={pdfUrl}>点击这里</a> 下载文件。
        </p>
      </object>
    </>
  );
};

PDFObject

官方文档:https://pdfobject.com/

这个方法原理就是基于 html 标签内嵌,只不过这个库可以自动检测浏览器兼容性,简化嵌入过程。

首先执行yarn add pdfobject @types/pdfobject安装包。

示例代码:

import React from "react";
import PdfUrl from "/path/to/file.pdf";
import PDFObject from "pdfobject";

// 生成一个指向本地 PDF 文件的绝对 URL
const pdfUrl = new URL(PdfUrl, import.meta.url).href;

export default () => {
  React.useEffect(() => {
    // 嵌入pdf
    PDFObject.embed(pdfUrl, "#pdfobject",{
      // 配置
    });
  }, []);

  return <div id="pdfobject" className="w-screen h-screen" />;
};

PDF.js 渲染

PDF.js 支持所有现代浏览器,包括移动端。有以下两种实现方式:

PDF.js 原生集成

示例代码:

import React from "react";
import PdfUrl from "/path/to/file.pdf";
import * as pdfjsLib from "pdfjs-dist";
import "pdfjs-dist/web/pdf_viewer.css";

const pdfUrl = new URL(PdfUrl, import.meta.url).href;

// 需要提供 PDF.js 工作线程,需要特定版本
pdfjsLib.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjsLib.version}/pdf.worker.min.mjs`;

export default () => {
  const pdfRef = React.useRef<HTMLDivElement>(null);

  React.useEffect(() => {
    const loadingTask = pdfjsLib.getDocument(pdfUrl);

    const initPdf = async () => {
      const pdf = await loadingTask.promise;
      for (let i = 1; i <= pdf.numPages; i++) {
        const page = await pdf.getPage(i);
        const viewport = page.getViewport({ scale: 1 });

        const canvas = window.document.createElement("canvas");
        pdfRef.current?.appendChild(canvas);
        canvas.width = viewport.width;
        canvas.height = viewport.height;

        const renderContext = {
          canvasContext: canvas.getContext("2d")!,
          viewport: viewport,
        };
        await page.render(renderContext).promise;
      }
    };

    initPdf();
  }, [props.type]);

  return <div ref={pdfRef} className="w-full h-full"></div>;
};

react-pdf 组件

示例代码:

import React from "react";
import PdfUrl from "/path/to/file.pdf";
import { Document, Page, pdfjs } from "react-pdf";
import "react-pdf/dist/Page/AnnotationLayer.css";
import "react-pdf/dist/Page/TextLayer.css";

// 生成一个指向本地 PDF 文件的绝对 URL
const pdfUrl = new URL(PdfUrl, import.meta.url).href;

// 需要提供 PDF.js 工作线程,需要特定版本
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`;

export default () => {
  const [numPages, setNumPages] = React.useState<number>();

  return (
    <>
      <Document
        options={{}}
        file={pdfUrl}
        onLoadSuccess={({ numPages: n }) => {
          setNumPages(n);
        }}
      >
        {Array.from(new Array(numPages), (_el, index) => (
          <Page
            key={`page_${index + 1}`}
            pageNumber={index + 1}
            width={window.innerWidth || document.documentElement.clientWidth}
          />
        ))}
      </Document>
    </>
  );
};

注意

这个方案可能会遇到以下问题:

location ~* \.mjs$ {
  types {
  }
  default_type 'application/javascript; charset=utf-8';
}
// @ts-ignore
if (typeof Promise.withResolvers === "undefined") {
  if (window)
    // @ts-ignore
    window.Promise.withResolvers = function () {
      let resolve, reject;
      const promise = new Promise((res, rej) => {
        resolve = res;
        reject = rej;
      });
      return { promise, resolve, reject };
    };
}

当然我们也可以使用core-js,直接引入即可:

import "core-js/actual/promise/with-resolvers";

注意:使用 @vitejs/plugin-legacy 时,需通过 include 选项特殊处理。

PDF 转图片

此方案通常需要服务端支持,将 PDF 转换为图片后返回给前端显示。这是兼容性最好的方案,但需要额外的服务端资源。

前端实现则仍需依赖 PDF.js,建议直接使用服务端转换方案。


Previous Post
H5支付前端接入
Next Post
Web文件上传