React项目中如何基于PDF.js实现单页缩放与旋转?
针对单个PDF页面独立缩放/旋转的解决方案
核心思路是给每个PDF页面维护独立的缩放、旋转状态,通过视口可见性检测锁定当前操作页面,仅对该页面应用自定义变换,其他页面保持默认状态。以下是基于PDF.js原生API的React实现方案:
实现步骤与代码示例
1. 依赖安装
首先确保安装PDF.js核心库:
npm install pdfjs-dist
2. React组件实现
import React, { useEffect, useRef, useState } from 'react'; import * as pdfjsLib from 'pdfjs-dist'; import 'pdfjs-dist/build/pdf.worker.entry'; // 配置PDF Worker路径 pdfjsLib.GlobalWorkerOptions.workerSrc = 'pdf.worker.js'; const SinglePagePDFViewer = ({ pdfUrl }) => { const [pdfDoc, setPdfDoc] = useState(null); const [pageCount, setPageCount] = useState(0); const [currentPage, setCurrentPage] = useState(1); // 存储每个页面的独立状态:{ 页码: { scale: 数值, rotation: 角度 } } const [pageStates, setPageStates] = useState({}); const pageCanvasRefs = useRef([]); const observerRef = useRef(null); // 加载PDF文档并初始化页面状态 useEffect(() => { const loadPDF = async () => { const doc = await pdfjsLib.getDocument(pdfUrl).promise; setPdfDoc(doc); setPageCount(doc.numPages); // 初始化所有页面的默认状态(缩放100%,旋转0度) const initialStates = {}; for (let i = 1; i <= doc.numPages; i++) { initialStates[i] = { scale: 1, rotation: 0 }; } setPageStates(initialStates); }; loadPDF(); }, [pdfUrl]); // 用Intersection Observer检测当前可见页面 useEffect(() => { if (pageCanvasRefs.current.length === 0) return; observerRef.current = new IntersectionObserver( (entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const pageNum = parseInt(entry.target.dataset.pageNum); setCurrentPage(pageNum); } }); }, { threshold: 0.5 } // 页面50%可见时判定为当前操作页 ); // 监听所有页面画布 pageCanvasRefs.current.forEach(ref => { if (ref) observerRef.current.observe(ref); }); return () => observerRef.current?.disconnect(); }, [pageCount]); // 渲染单个页面 const renderPage = async (pageNum, canvasRef) => { if (!pdfDoc || !canvasRef) return; const page = await pdfDoc.getPage(pageNum); const { scale, rotation } = pageStates[pageNum]; // 根据当前页面的缩放/旋转生成视口 const viewport = page.getViewport({ scale, rotation }); const canvas = canvasRef; const ctx = canvas.getContext('2d'); canvas.height = viewport.height; canvas.width = viewport.width; await page.render({ canvasContext: ctx, viewport }).promise; }; // 缩放当前页面(±20%,最小50%) const adjustZoom = (direction) => { setPageStates(prev => ({ ...prev, [currentPage]: { ...prev[currentPage], scale: Math.max(0.5, prev[currentPage].scale + (direction === 'in' ? 0.2 : -0.2)) } })); }; // 旋转当前页面(每次+90度,循环360度) const rotatePage = () => { setPageStates(prev => ({ ...prev, [currentPage]: { ...prev[currentPage], rotation: (prev[currentPage].rotation + 90) % 360 } })); }; if (!pdfDoc) return <div>PDF加载中...</div>; return ( <div className="pdf-container"> {/* 操作控制面板 */} <div className="pdf-controls"> <button onClick={() => adjustZoom('out')}>缩小</button> <span>{Math.round(pageStates[currentPage].scale * 100)}%</span> <button onClick={() => adjustZoom('in')}>放大</button> <button onClick={rotatePage}>旋转90°</button> <span>当前页:{currentPage}/{pageCount}</span> </div> {/* PDF页面容器 */} <div className="pages-wrapper"> {Array.from({ length: pageCount }, (_, idx) => { const pageNum = idx + 1; return ( <div key={pageNum} className="page-container"> <canvas ref={el => pageCanvasRefs.current[idx] = el} data-page-num={pageNum} onLoad={() => renderPage(pageNum, pageCanvasRefs.current[idx])} style={{ // 非当前页灰化区分 opacity: currentPage === pageNum ? 1 : 0.7, transition: 'opacity 0.3s' }} /> </div> ); })} </div> </div> ); }; export default SinglePagePDFViewer;
3. 关键细节说明
- 独立状态隔离:通过
pageStates对象存储每个页面的缩放、旋转值,确保操作单个页面时不影响其他页面。 - 可见性自动检测:利用Intersection Observer API自动识别视口中的页面,无需手动切换当前页。
- 性能优化:可以扩展实现懒加载(仅渲染当前页前后2-3页),避免大量DOM节点导致的性能问题。
- 样式适配:旋转页面后可通过CSS调整页面容器的布局,避免内容溢出或排版错乱。
内容的提问来源于stack exchange,提问作者Ahmed Kallali
相关产品推荐
相关产品推荐

