You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 20:32:37