该方案基于react + vite
。
如果我们要在项目中实现 pdf 预览的效果。一共有三种方案进行选择
- PDF 嵌入(移动端不支持)
PDF.js
渲染- PDF 转图片显示
下面将展示三种方法的具体使用。
PDF 嵌入
此方案需要浏览器原生支持 PDF 预览。截至 2024 年,大多数移动设备都不支持此方案。主要有两种实现方式:
html 标签
可使用 iframe
、embed
或 object
标签引用 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
这个方法原理就是基于 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>
</>
);
};
注意
这个方案可能会遇到以下问题:
- 若遇到
.mjs
文件 MIME 类型问题,需在Nginx
中添加:
location ~* \.mjs$ {
types {
}
default_type 'application/javascript; charset=utf-8';
}
- 有的浏览器我们可能会遇到,无法使用
Promise.withResolvers
的错误,这是因为pdf.worker.min.mjs
用到了这个接口,使用legacy
可能也无法解决,这就需要我们手动进行 polyfill:
// @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,建议直接使用服务端转换方案。