React中动态切换PDF预览时页面滚动位置会重置怎么办?

a'ゞ怡博 阅读 29

在用react-pdf做动态PDF预览时遇到个怪问题:Viewer组件切换不同PDF文件后,页面总会强制跳到顶部。我试过用useState保存scrollTop值,但组件重新渲染后还是无效…

具体场景是点击不同文件时动态更新document状态,代码类似这样:


import { Document, Page, pdfjs } from 'react-pdf';

function PdfPreview({ selectedFile }) {
  const [numPages, setNumPages] = useState(0);
  const [pdfDoc, setPdfDoc] = useState(null);

  useEffect(() => {
    pdfjs.GlobalWorkerOptions.workerSrc = '//cdn.jsdelivr.net/npm/pdfjs-dist@3.4.120/build/pdf.worker.min.js';
    const loadPdf = async () => {
      const pdf = await pdfjs.getDocument(selectedFile).promise;
      setPdfDoc(pdf);
      setNumPages(pdf.numPages);
    };
    loadPdf();
  }, [selectedFile]);

  return (
    <div>
      {[...Array(numPages)].map((_, i) => (
        <Page key={i} pageNumber={i + 1} />
      ))}
    </div>
  );
}

当切换selectedFile时整个预览区域重新加载,之前滚动到中间的位置就会消失。用window.scrollTo强行定位又会导致渲染不同步的问题…

我来解答 赞 5 收藏
二维码
手机扫码查看
2 条解答
UI家乐
UI家乐 Lv1
这个问题其实挺常见的,核心在于切换PDF后整个内容区被重新渲染,滚动位置自然就重置了。

你用useState保存scrollTop无效的原因是:状态保存的是值,但组件重新mount后DOM重新生成,滚动是针对具体的容器元素的,你需要在正确的时机把保存的值应用回去。

简单直接的解决方案是用useRef保存滚动容器的引用,在PDF加载完成恢复:

function PdfPreview({ selectedFile }) {
const [numPages, setNumPages] = useState(0);
const [pdfDoc, setPdfDoc] = useState(null);
const scrollRef = useRef(null);
const prevScrollTop = useRef(0);

useEffect(() => {
// 切换前保存滚动位置
if (scrollRef.current) {
prevScrollTop.current = scrollRef.current.scrollTop;
}

pdfjs.GlobalWorkerOptions.workerSrc = '//cdn.jsdelivr.net/npm/pdfjs-dist@3.4.120/build/pdf.worker.min.js';

const loadPdf = async () => {
const pdf = await pdfjs.getDocument(selectedFile).promise;
setPdfDoc(pdf);
setNumPages(pdf.numPages);
};
loadPdf();
}, [selectedFile]);

// PDF加载完成后恢复滚动位置
useEffect(() => {
if (numPages > 0 && scrollRef.current) {
// 用requestAnimationFrame确保DOM渲染完成后再滚动
requestAnimationFrame(() => {
if (scrollRef.current) {
scrollRef.current.scrollTop = prevScrollTop.current;
}
});
}
}, [numPages]);

return (


{[...Array(numPages)].map((_, i) => (

))}


);
}


几个关键点:

滚动容器需要有个ref绑定,切换前用ref获取当前滚动位置并存到ref里(注意是ref不是state,因为不需要触发重新渲染)。然后在numPages变化(说明PDF加载完成)后,用requestAnimationFrame延迟执行scrollTop赋值,这样能确保Page组件已经渲染到DOM里了。

如果你的滚动是针对window的,把ref绑定去掉,直接用document.documentElement.scrollTop就行。

还有个小建议:pdfjs的workerSrc建议放到组件外部或者useEffect的依赖数组里只执行一次,不然每次selectedFile变化都会重复设置。
点赞
2026-03-12 07:00
爱学习的爱军
这个问题的核心是组件重新渲染导致DOM被销毁重建,所以滚动位置会丢失。解决思路是把PDF预览区域的渲染和文件加载解耦,用条件渲染保住DOM结构。

直接用这个:

import { Document, Page, pdfjs } from 'react-pdf';

function PdfPreview({ selectedFile }) {
const [numPages, setNumPages] = useState(0);
const [pdfDoc, setPdfDoc] = useState(null);
const containerRef = useRef();

useEffect(() => {
pdfjs.GlobalWorkerOptions.workerSrc = '//cdn.jsdelivr.net/npm/pdfjs-dist@3.4.120/build/pdf.worker.min.js';
let isMounted = true;
const loadPdf = async () => {
const pdf = await pdfjs.getDocument(selectedFile).promise;
if (isMounted) {
setPdfDoc(pdf);
setNumPages(pdf.numPages);
}
};
loadPdf();
return () => { isMounted = false; };
}, [selectedFile]);

// 关键在这里,用null占位保持DOM结构
return (

{!pdfDoc &&
Loading...
}
{pdfDoc && [...Array(numPages)].map((_, i) => (

))}

);
}


要点是用了一个ref容器保存滚动状态,同时在加载新PDF时用loading占位符撑住高度。这样即使pdfDoc置空了,外层容器的滚动位置也不会丢。

记得给外层容器设置固定高度和overflow:auto,不然滚动行为会有问题。另外加了个isMounted避免异步回调里的setState导致内存泄漏,这在实际项目里很重要。

试了下这方案挺稳的,页面切换时滚动位置能正常保持。
点赞 8
2026-02-17 00:05