pdfjs4

发布于:2025-04-19 ⋅ 阅读:(15) ⋅ 点赞:(0)

import React, { useEffect, useState, useRef } from 'react';
import * as pdfjsLib from 'pdfjs-dist';

pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdf.worker.min.js';

const PDFViewer = ({ url }) => {
  const [pdf, setPdf] = useState(null);
  const [currentPage, setCurrentPage] = useState(1);
  const [numPages, setNumPages] = useState(0);
  const [pageRendering, setPageRendering] = useState(false);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [zoom, setZoom] = useState(1);
  const canvasRef = useRef(null);
  const linkLayerRef = useRef(null);
  const containerRef = useRef(null);
  const touchStartRef = useRef({ x: 0, y: 0, distance: 0 });
  const lastTapRef = useRef(0);

  // ... addWatermark 和 setupLinkLayer 函数保持不变 ...

  // 处理移动端手势
  const handleTouchStart = (e) => {
    if (e.touches.length === 2) {
      // 双指缩放
      const distance = Math.hypot(
        e.touches[0].clientX - e.touches[1].clientX,
        e.touches[0].clientY - e.touches[1].clientY
      );
      touchStartRef.current = {
        x: (e.touches[0].clientX + e.touches[1].clientX) / 2,
        y: (e.touches[0].clientY + e.touches[1].clientY) / 2,
        distance
      };
    } else if (e.touches.length === 1) {
      // 单指点击
      const now = Date.now();
      if (now - lastTapRef.current < 300) {
        // 双击缩放
        e.preventDefault();
        handleDoubleTap(e.touches[0].clientX, e.touches[0].clientY);
      }
      lastTapRef.current = now;
    }
  };

  const handleTouchMove = (e) => {
    if (e.touches.length === 2) {
      e.preventDefault();
      const distance = Math.hypot(
        e.touches[0].clientX - e.touches[1].clientX,
        e.touches[0].clientY - e.touches[1].clientY
      );
      const scale = distance / touchStartRef.current.distance;
      const newZoom = Math.min(Math.max(zoom * scale, 0.5), 3.0);
      setZoom(newZoom);
    }
  };

  const handleDoubleTap = (x, y) => {
    if (zoom > 1) {
      setZoom(1); // 重置缩放
    } else {
      setZoom(2); // 放大到2倍
    }
  };

  // 监听屏幕方向变化
  useEffect(() => {
    const handleOrientationChange = () => {
      // 延迟执行以等待布局完成
      setTimeout(() => {
        if (containerRef.current) {
          renderPage(currentPage);
        }
      }, 100);
    };

    // 监听屏幕方向变化
    window.addEventListener('orientationchange', handleOrientationChange);
    
    // 监听视口大小变化(某些设备需要)
    const mediaQuery = window.matchMedia('(orientation: portrait)');
    mediaQuery.addListener(handleOrientationChange);

    return () => {
      window.removeEventListener('orientationchange', handleOrientationChange);
      mediaQuery.removeListener(handleOrientationChange);
    };
  }, [currentPage]);

  // 修改渲染页面函数
  const renderPage = async (pageNum) => {
    if (pageRendering || !pdf) return;

    setPageRendering(true);
    
    try {
      if (!pageCache.current.has(pageNum)) {
        const page = await pdf.getPage(pageNum);
        pageCache.current.set(pageNum, page);
      }
      
      const page = pageCache.current.get(pageNum);
      const canvas = canvasRef.current;
      const ctx = canvas.getContext('2d');

      // 获取容器宽度(考虑移动端视口)
      const containerWidth = containerRef.current.clientWidth;
      const viewport = page.getViewport({ scale: 1 });
      
      // 计算缩放比例(考虑设备像素比)
      const devicePixelRatio = window.devicePixelRatio || 1;
      const baseScale = (containerWidth / viewport.width);
      const finalScale = baseScale * zoom * devicePixelRatio;

      // 设置canvas尺寸
      const scaledViewport = page.getViewport({ scale: finalScale });
      canvas.width = scaledViewport.width;
      canvas.height = scaledViewport.height;
      
      // 设置显示尺寸
      const displayWidth = containerWidth * zoom;
      const displayHeight = (containerWidth * viewport.height / viewport.width) * zoom;
      canvas.style.width = `${displayWidth}px`;
      canvas.style.height = `${displayHeight}px`;

      // 更新链接层
      if (linkLayerRef.current) {
        linkLayerRef.current.style.width = `${displayWidth}px`;
        linkLayerRef.current.style.height = `${displayHeight}px`;
      }

      // 渲染PDF
      const renderContext = {
        canvasContext: ctx,
        viewport: scaledViewport,
        enableWebGL: true,
      };

      await page.render(renderContext).promise;
      setupLinkLayer(page, scaledViewport);
      addWatermark(canvas, finalScale);
    } catch (error) {
      console.error('Error rendering page:', error);
      setError('页面渲染失败,请刷新重试');
    } finally {
      setPageRendering(false);
    }
  };

  // 返回的 JSX
  return (
    <div className="pdf-viewer">
      <div className="pdf-controls">
        <div className="navigation-controls">
          <button 
            onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))}
            disabled={currentPage <= 1 || pageRendering}
            className="control-button"
          >
            上一页
          </button>
          <span className="page-info">{`${currentPage} / ${numPages}`}</span>
          <button 
            onClick={() => setCurrentPage(prev => Math.min(prev + 1, numPages))}
            disabled={currentPage >= numPages || pageRendering}
            className="control-button"
          >
            下一页
          </button>
        </div>
      </div>
      <div 
        className="pdf-container" 
        ref={containerRef}
        onTouchStart={handleTouchStart}
        onTouchMove={handleTouchMove}
      >
        <div className="canvas-container">
          <canvas ref={canvasRef} className="pdf-canvas" />
          <div 
            ref={linkLayerRef}
            className="link-layer"
          />
        </div>
      </div>
    </div>
  );
};

export default PDFViewer;


网站公告

今日签到

点亮在社区的每一天
去签到