mobile预览

import { useEffect, useRef, useState } from 'react';
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf';
import 'pdfjs-dist/web/pdf_viewer.css';

// 配置 worker
pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdf.worker.min.js'; // 注意这里路径!

type Props = {
  fileData: ArrayBuffer; // 后端返回的文件流
};

export default function LazyPdfViewer({ fileData }: Props) {
  const containerRef = useRef<HTMLDivElement>(null);
  const [pdf, setPdf] = useState<pdfjsLib.PDFDocumentProxy | null>(null);
  const observerRef = useRef<IntersectionObserver | null>(null);

  useEffect(() => {
    const loadingTask = pdfjsLib.getDocument({ data: fileData });
    loadingTask.promise.then(setPdf);
  }, [fileData]);

  useEffect(() => {
    if (!pdf || !containerRef.current) return;

    const container = containerRef.current;

    const renderPage = async (pageDiv: HTMLDivElement, pageNumber: number) => {
      if (pageDiv.dataset.rendered) return; // 防止重复渲染
      pageDiv.dataset.rendered = 'true';

      const page = await pdf.getPage(pageNumber);

      const scale = window.innerWidth < 768 ? 1.2 : 1.5; // 手机小一点
      const viewport = page.getViewport({ scale });

      // 创建canvas
      const canvas = document.createElement('canvas');
      canvas.width = viewport.width;
      canvas.height = viewport.height;
      canvas.style.width = `${viewport.width}px`;
      canvas.style.height = `${viewport.height}px`;
      const context = canvas.getContext('2d')!;

      // 渲染PDF页到canvas
      await page.render({ canvasContext: context, viewport }).promise;

      // 渲染超链接
      const annotationLayerDiv = document.createElement('div');
      annotationLayerDiv.className = 'annotationLayer';
      const annotations = await page.getAnnotations();
      pdfjsLib.AnnotationLayer.render({
        viewport,
        div: annotationLayerDiv,
        annotations,
        page,
        linkService: new pdfjsLib.SimpleLinkService(),
        renderInteractiveForms: true,
      });

      pageDiv.appendChild(canvas);
      pageDiv.appendChild(annotationLayerDiv);
    };

    const unrenderPage = (pageDiv: HTMLDivElement) => {
      pageDiv.innerHTML = '';
      pageDiv.dataset.rendered = '';
    };

    observerRef.current?.disconnect();

    observerRef.current = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          const pageDiv = entry.target as HTMLDivElement;
          const pageNumber = Number(pageDiv.dataset.pageNumber);
          if (entry.isIntersecting) {
            renderPage(pageDiv, pageNumber);
          } else {
            unrenderPage(pageDiv); // 不可见了卸载
          }
        });
      },
      {
        root: container,
        rootMargin: '200px 0px', // 提前加载
        threshold: 0.1,
      }
    );

    // 创建每页的div
    container.innerHTML = '';
    for (let i = 1; i <= pdf.numPages; i++) {
      const pageDiv = document.createElement('div');
      pageDiv.className = 'pdf-page';
      pageDiv.dataset.pageNumber = String(i);
      pageDiv.style.minHeight = '100vh'; // 初始撑开
      container.appendChild(pageDiv);
      observerRef.current.observe(pageDiv);
    }
  }, [pdf]);

  return (
    <div
      ref={containerRef}
      style={{
        overflowY: 'auto',
        height: '100vh',
        position: 'relative',
      }}
    />
  );
}
 

.pdf-page {
  position: relative;
  margin: 20px 0;
}
.annotationLayer {
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: auto;
}
canvas {
  display: block;
  margin: 0 auto;
}
 

import LazyPdfViewer from './LazyPdfViewer';
import { useState, useEffect } from 'react';

export default function App() {
  const [pdfData, setPdfData] = useState<ArrayBuffer | null>(null);

  useEffect(() => {
    // 模拟后端取文件流
    fetch('/your-pdf-url.pdf')
      .then((res) => res.arrayBuffer())
      .then(setPdfData);
  }, []);

  return (
    <div>
      {pdfData ? <LazyPdfViewer fileData={pdfData} /> : <p>Loading...</p>}
    </div>
  );
}
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值