React--》实现 PDF 文件的预览操作

发布于:2025-07-30 ⋅ 阅读:(24) ⋅ 点赞:(0)

PDF作为最常见的电子文档格式如何实现电子签名和盖章功能,成为了每个开发者和企业必须面对的问题。在这篇文章中将深入探讨如何在React应用中集成PDF预览功能

要实现对pdf进行预览操作首先我们要实现对pdf的预览操作,实现预览操作的方式有很多如使用pdfjs-dist、react-pdf、pdf-viewer等预览工具,它们之间的区别如下所示:

1)pdfjs-dist:基于Mozilla的pdf.js项目的js库,提供了丰富的API且允许开发者对pdf文件进行各种操作,如缩放、旋转、搜索等,功能强大兼容性好,支持PC端和移动端浏览器。

2)react-pdf:专门为react框架设计的pdf渲染库,它基pdfjs-dist但提供了更加简洁易用的react组件接口,通过react-pdf可以轻松地在React应用中实现PDF预览功能。

3)pdf-viewer:基于vue的pdf渲染组件,与react-pdf类似也提供了简洁易用的API,方便开发者在vue应用中实现pdf预览功能,优点是易于集成和使用同时支持vue的响应式布局和组件化开发。

pdfjs-dist、react-pdf和pdf-viewer都是实现前端PDF预览功能的优秀工具,它们各有优缺点,选择哪个取决于你的具体需求和技术栈,因为此次采用的react框架,所以这里我们就使用react-pdf进行预览操作,预览的样式布局我们就参考e签宝:地址 的样式布局来进行实现吧,如下所示:

终端执行如下命令按照pdf预览插件,关于这个插件详细具体的使用,可以参考:npm 或 官网 进行查看,这里不再赘述:

npm i react-pdf

pdf预览:安装完成之后,我们在网上随便下载一个pdf资料来进行演示,这里我们要使用react-pdf进行预览就要引入配置pdf的worker文件,然后通过Document设置文档容器,Page设置pdf每一页的内容,如下代码所示:

import { useState, useRef } from 'react';
import { pdfjs, Document, Page } from 'react-pdf';
import 'react-pdf/dist/esm/Page/AnnotationLayer.css'
import 'react-pdf/dist/esm/Page/TextLayer.css';
import "./index.less"
// 配置 PDF.js 的 worker 文件
pdfjs.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();

const PDFPreview = () => {
    const [numPages, setNumPages] = useState<number>(0);
    const [pdfWidth, setPdfWidth] = useState<number>(600); // 默认宽度为 600px
    const pdfWidthRef = useRef(pdfWidth); // 用于存储和防止频繁更新 pdfWidth 的 ref
    const pageRefs = useRef<Array<HTMLDivElement | null>>([]);
    return (
        <div className='pdf-container'>
            <div className='pdf-center'>
                <div className='pdf-center-bottom' style={{ overflowY: 'auto' }}>
                    <Document
                        file={new URL('/public/test.pdf', import.meta.url).toString()}
                        onLoadSuccess={({ numPages }) => setNumPages(numPages)}
                        onLoadError={(error) => console.log('Error loading PDF:', error)}
                    >
                        {numPages && Array.from(new Array(numPages), (el, index) => (
                            <div 
                                key={`page-container_${index + 1}`} 
                                className='pdf-content-item'
                                ref={(ref: any) => pageRefs.current[index] = ref}
                            >
                                <Page
                                    width={pdfWidthRef.current} // 使用 ref 存储的宽度
                                    key={`page_${index + 1}`}
                                    pageNumber={index + 1}
                                    onLoadSuccess={({ width }) => setPdfWidth(width)}
                                />
                            </div>
                        ))}
                    </Document>
                </div>
            </div>
        </div>
    );
};

export default PDFPreview;

效果如下所示:

布局调整:接下来我们仿照e签宝的样式来对pdf预览进行调整布局,这里我们用到了antd和其对应的icon内容,可以提前安装一下对应的依赖,最终实现的效果如下所示:

<div className='pdf-container'>
    <div className='pdf-left'>
        <div className='pdf-left-top'>操作内容</div>
    </div>
    <div className='pdf-center'>
        <div className='pdf-center-top'>
            <div className='slider'>
                <Slider value={percentage} min={50} max={300} step={10} 
                    tooltip={{ formatter: null }} style={{ width: '150px' }}
                    onChange={handlePercentageChange} 
                />
                <div className='slider-value'>{percentage}%</div>
            </div>
            <div className='pagination'>
                <Pagination simple total={numPages} pageSize={1} onChange={handlePageChange} />
            </div>
            <IsFullScreen />
        </div>
        <div className='pdf-center-bottom' ref={scrollContainerRef} style={{ overflowY: 'auto' }}>
            <Document
                loading={<IsLoading />}
                file={new URL('/public/test.pdf', import.meta.url).toString()}
                onLoadSuccess={({ numPages }) => setNumPages(numPages)}
                onLoadError={(error) => console.log('Error loading PDF:', error)}
            >
                {numPages && Array.from(new Array(numPages), (el, index) => (
                    <div 
                        key={`page-container_${index + 1}`} 
                        className='pdf-content-item'
                        ref={(ref: any) => pageRefs.current[index] = ref}
                    >
                        <Page
                            width={pdfWidthRef.current} // 使用 ref 存储的宽度
                            key={`page_${index + 1}`}
                            pageNumber={index + 1}
                            onLoadSuccess={({ width }) => setPdfWidth(width)}
                        />
                    </div>
                ))}
            </Document>
        </div>
        <div className='operate'>
            <Button icon={<VerticalAlignTopOutlined />} onClick={scrollToTop}></Button>
            <Button icon={<VerticalAlignBottomOutlined />} onClick={scrollToBottom}></Button>
        </div>
    </div>
    <div className='pdf-right'>
        <div className='pdf-right-top'>位置信息</div>
    </div>
</div>

基础操作:接下来开始实现预览pdf顶部的一些按钮操作,实现放大缩小、切换分页、回到顶部底部操作,核心逻辑就是改变pdf预览的宽度以及切换滚动距离,很容易就实现了,代码如下所示:

// 防抖处理滑块值变化
const handlePercentageChange = useCallback(
    debounce((value: number) => {
        setPercentage(value);
        const newWidth = 600 * (value / 100);
        setPdfWidth(newWidth);
        pdfWidthRef.current = newWidth;
    }, 100), []
);
const handlePageChange = (pageNumber: number) => {
    const targetPageRef = pageRefs.current[pageNumber - 1];
    if (targetPageRef && scrollContainerRef.current) {
        const offsetTop = targetPageRef.offsetTop;
        scrollContainerRef.current.scrollTop = offsetTop -80;
    }
};
// 平滑滚动到顶部
const scrollToTop = () => {
    if (scrollContainerRef.current) {
        smoothScrollTo(0);
    }
};
// 平滑滚动到底部
const scrollToBottom = () => {
    if (scrollContainerRef.current) {
        const maxScrollHeight = scrollContainerRef.current.scrollHeight;
        smoothScrollTo(maxScrollHeight);
    }
};
// 平滑滚动到指定位置
const smoothScrollTo = (targetPosition: number) => {
    if (scrollContainerRef.current) {
        const currentPosition = scrollContainerRef.current.scrollTop;
        const distance = targetPosition - currentPosition;
        const duration = 500; // 滚动持续时间(毫秒)
        let startTime: number;
        const scrollStep = (timestamp: number) => {
            if (!startTime) startTime = timestamp;
            const progress = timestamp - startTime;
            const increment = distance * (progress / duration);
            scrollContainerRef.current.scrollTop = currentPosition + increment;
            if (progress < duration) {
                requestAnimationFrame(scrollStep);
            } else {
                scrollContainerRef.current.scrollTop = targetPosition;
            }
        };
        requestAnimationFrame(scrollStep);
    }
};

全屏操作:对于全屏操作这里我封装了个组件,然后直接引入该组件进行使用就行了,如下所示:

import { useState } from "react";
import { ArrowsAltOutlined, ShrinkOutlined } from "@ant-design/icons";

const IsFullScreen = () => {
  const [isFullScreen, setIsFullScreen] = useState(false);

  // 进入全屏模式的函数
  const enterFullScreen = () => {
    const elem: any = document.documentElement; // 获取整个文档元素
    if (elem.requestFullscreen) {
      elem.requestFullscreen(); // 标准全屏
    } else if (elem.mozRequestFullScreen) {
      elem.mozRequestFullScreen(); // Firefox
    } else if (elem.webkitRequestFullscreen) {
      elem.webkitRequestFullscreen(); // Chrome, Safari 和 Opera
    } else if (elem.msRequestFullscreen) {
      elem.msRequestFullscreen(); // Internet Explorer / Edge
    }
    setIsFullScreen(true); // 更新状态,进入全屏
  };
  // 退出全屏模式的函数
  const exitFullScreen = () => {
    const elem: any = document; // 获取整个文档元素
    if (elem.exitFullscreen) {
        elem.exitFullscreen(); // 退出全屏
    } else if (elem.mozCancelFullScreen) {
        elem.mozCancelFullScreen(); // Firefox
    } else if (elem.webkitExitFullscreen) {
        elem.webkitExitFullscreen(); // Chrome, Safari 和 Opera
    } else if (elem.msExitFullscreen) {
        elem.msExitFullscreen(); // Internet Explorer / Edge
    }
    setIsFullScreen(false); // 更新状态,退出全屏
  };
  return (
    <div style={{ cursor: 'pointer' }} onClick={isFullScreen ? exitFullScreen : enterFullScreen}>
      {isFullScreen ? <ShrinkOutlined /> : <ArrowsAltOutlined />}
    </div>
  );
};

export default IsFullScreen;

至此对于pdf的预览以及一些基础操作完成了,感兴趣的朋友可以继续开发其他功能!


网站公告

今日签到

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