介绍如何使用 pdf.js 在 React 应用中构建一个简约的 PDF 查看器。
上效果图
上代码
import { useEffect, useRef, useState } from 'react';
import * as PDFJS from 'pdfjs-dist';
import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
PDFJS.GlobalWorkerOptions.workerSrc =
'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.6.82/pdf.worker.min.mjs';
interface Props {
fileUrl: string;
}
const PDFViewer = ({ fileUrl }: Props) => {
const [numPages, setNumPages] = useState<number>(0); // 页数状态
const pdfRefs = useRef<(HTMLCanvasElement | null)[]>([]); // 用于存储每一页的canvas引用
const observer = useRef<IntersectionObserver | null>(null); // IntersectionObserver引用
// 渲染指定页面
const renderPage = (page: any, canvas: HTMLCanvasElement) => {
const viewport = page.getViewport({ scale: 1 });
if (canvas) {
// 确保canvas有效
canvas.width = viewport.width;
canvas.height = viewport.height;
const pdfCtx = canvas.getContext('2d');
if (pdfCtx) {
page.render({
canvasContext: pdfCtx,
viewport: viewport,
});
}
}
};
// 处理页面加载
const loadPage = (index: number) => {
if (index < numPages) {
PDFJS.getDocument(fileUrl).promise.then((pdfDoc) => {
pdfDoc.getPage(index + 1).then((page) => {
const canvas = pdfRefs.current[index];
if (canvas) {
// 确保canvas不为null
renderPage(page, canvas); // 渲染页面
}
});
});
}
};
useEffect(() => {
// 获取PDF文档
PDFJS.getDocument(fileUrl).promise.then((pdfDoc) => {
setNumPages(pdfDoc.numPages); // 设置页面数量
// 创建Intersection Observer
observer.current = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const index = Number(entry.target.getAttribute('data-index'));
loadPage(index); // 加载页面
observer.current?.unobserve(entry.target); // 加载后取消观察
}
});
});
// 观察每一页
pdfRefs.current.forEach((canvas, index) => {
if (canvas) {
canvas.setAttribute('data-index', index.toString());
observer.current?.observe(canvas); // 使用可选链确保不是null
}
});
});
return () => {
observer.current?.disconnect(); // 使用可选链确保不是null
};
}, [fileUrl, numPages]);
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
width: '100%',
height: '100%',
overflowY: 'auto',
}}
>
{Array.from({ length: numPages }, (_, index) => (
<canvas
key={index}
ref={(el) => (pdfRefs.current[index] = el)} // 存储每一页的canvas引用
style={{ marginBottom: '20px', display: 'block' }} // 添加每页之间的间隔
></canvas>
))}
</div>
);
};
export default PDFViewer;
主要还是注意这段代码,在使用之前先看看这个链接能不能访问。无法访问的话就重新去pdf.js
官网重新拿一个,标记了框框的那个是pdfjs-dist的版本号。
为了避免一次性渲染所有 PDF 页面,我引入了 Intersection Observer
。当用户滚动浏览 PDF 文档时,只有当前视口中的页面才会被渲染,这大大减少了不必要的资源消耗。
我也是第一次写预览pdf这个需求,写的不好还请见谅。