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

使用react-pdf时如何获取当前滚动的页码?

解决react-pdf滚动时显示当前页码的问题

你之前用handlePageChange和handlePageRenderSuccess无效,是因为这两个事件是页面加载/渲染完成的触发事件,和滚动到该页的行为无关。要实现滚动时显示当前页码,需要监听滚动容器的滚动事件,检测哪个Page元素处于视口范围内。

具体修改步骤:

  1. 添加滚动容器的Ref,用于获取容器元素并监听滚动
  2. 编写滚动监听函数,判断当前可见的页码
  3. 在组件挂载时绑定滚动事件,卸载时移除

修改后的完整代码:

import React, { useState, useRef, useEffect } from 'react';
import styles from './Previewer.module.css';
import { toast } from 'react-toastify';
import { Document, Page, pdfjs } from 'react-pdf';
import { PDFDocumentProxy } from 'pdfjs-dist';
pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.js`;
import 'react-pdf/dist/Page/TextLayer.css';
import 'react-pdf/dist/Page/AnnotationLayer.css';

export type ViewerProps = {
    pdfUrl: string | undefined;
};

const Previewer: React.FC<ViewerProps> = (props: ViewerProps) => {

    const [pdfScale, setPdfScale] = useState<number>(1);
    const [numPages, setNumPages] = useState<number>();
    const [pageNumber, setPageNumber] = useState<number>(1);
    // 滚动容器的Ref
    const previewBodyRef = useRef<HTMLDivElement>(null);

    const options = {
        cMapUrl: 'https://cdn.jsdelivr.net/npm/pdfjs-dist@3.9.179/cmaps/',
    };

    if (!props.pdfUrl) {
        return (<div>Loading</div>);
    }

    const handleDownloadPdf = async (pdfUrl: any) => {
        if (!pdfUrl) {
            toast.error("PDF Url is null");
            return;
        }
        try {
            const response = await fetch(pdfUrl);
            const blob = await response.blob();
            const downloadUrl = window.URL.createObjectURL(new Blob([blob]));
            const link = document.createElement('a');
            link.href = downloadUrl;
            link.setAttribute('download', 'main.pdf');
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        } catch (error) {
            console.error('Error downloading PDF:', error);
        }
    }

    const handleZoomIn = async () => {
        setPdfScale(pdfScale + 0.1);
    }

    const handleZoomOut = async () => {
        setPdfScale(pdfScale - 0.1);
    }

    const onDocumentLoadSuccess = (pdf: any) => {
        const { numPages } = pdf;
        setNumPages(numPages);
    }

    // 滚动监听:检测当前可见页码
    const handleScroll = () => {
        if (!previewBodyRef.current || !numPages) return;
        
        const pages = previewBodyRef.current.querySelectorAll(`.${styles.pdfPage}`);
        const viewportTop = previewBodyRef.current.scrollTop;
        const viewportBottom = viewportTop + previewBodyRef.current.clientHeight;
        let currentVisiblePage = 1;

        pages.forEach((page, index) => {
            const pageRect = page.getBoundingClientRect();
            // 计算页面相对于滚动容器的位置
            const pageTop = pageRect.top + previewBodyRef.current!.scrollTop;
            const pageBottom = pageTop + pageRect.height;

            // 判断页面是否处于视口核心区域(避免边缘露出时误判)
            const isVisible = pageTop < viewportBottom - pageRect.height / 3 
                            && pageBottom > viewportTop + pageRect.height / 3;
            
            if (isVisible) {
                currentVisiblePage = index + 1;
            }
        });

        if (currentVisiblePage !== pageNumber) {
            setPageNumber(currentVisiblePage);
        }
    }

    // 绑定/解绑滚动事件
    useEffect(() => {
        const bodyElement = previewBodyRef.current;
        if (bodyElement) {
            bodyElement.addEventListener('scroll', handleScroll);
            // 初始加载时检测一次当前页码
            setTimeout(handleScroll, 100);
        }

        return () => {
            if (bodyElement) {
                bodyElement.removeEventListener('scroll', handleScroll);
            }
        };
    }, [numPages, pageNumber, styles.pdfPage]);

    const renderPages = (totalPageNum: number | undefined) => {
        if (!totalPageNum || totalPageNum < 1) return;
        const tagList: JSX.Element[] = [];
        for (let i = 1; i <= totalPageNum; i++) {
            tagList.push(
                <Page key={i}
                    className={styles.pdfPage}
                    scale={pdfScale}
                    pageNumber={i} >
                </Page>
            );
        }
        return tagList;
    }

    return (
        <div id="preview" className={styles.preview}>
            <div className={styles.previewHader}>
                <button onClick={() => { handleDownloadPdf(props.pdfUrl) }}>下载PDF</button>
                <button id="zoominbutton" onClick={() => { handleZoomIn() }}>放大</button>
                <button id="zoomoutbutton" onClick={() => { handleZoomOut() }}>缩小</button>
            </div>
            {/* 绑定滚动容器的Ref */}
            <div ref={previewBodyRef} className={styles.previewBody}>
                <Document options={options} file={props.pdfUrl} onLoadSuccess={onDocumentLoadSuccess}>
                    {renderPages(numPages)}
                </Document>
            </div>
            <div className={styles.previewFooter}>
                <p>
                    Page {pageNumber} of {numPages}
                </p>
            </div>
        </div>
    );
}

export default Previewer;

关键说明:

  • 通过previewBodyRef获取滚动容器,监听其scroll事件
  • 在handleScroll中,用getBoundingClientRect计算每个Page的位置,判断是否处于视口核心区域(避免页面边缘露出时误判)
  • 初始加载时通过setTimeout调用一次handleScroll,确保页码显示正确
  • 移除了无用的handlePageChange和handlePageRenderSuccess函数,它们和滚动行为无关

内容的提问来源于stack exchange,提问作者Dolphin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 19:58:11