wojtekmaj / react-pdf

Display PDFs in your React app as easily as if they were images.
https://projects.wojtekmaj.pl/react-pdf
MIT License
9.01k stars 861 forks source link

Flickering issue when scaling the entire pdf #1760

Closed ShashanKV98 closed 1 month ago

ShashanKV98 commented 3 months ago

Before you start - checklist

Description

Hi. I had some issues when dealing with zooming a document with all pages in a column (not just a single page). I browsed the issues in this repo, and found a sandbox link from @wojtekmaj https://github.com/wojtekmaj/react-pdf/issues/875#issuecomment-977975073.

This was for a single page in the viewport, so I extended it to include all the pages. The first page seems to render just fine as I use the slider, but the rest of the pages tend to flicker a lot. I've been on this for days but couldn't figure out if this is because of React or page.render refreshing the entire page from scratch at every new scale value or something else that I'm missing.

I'm also curious about the recommended approach for handling zooming with this library. Is it possible to get it close to how pdf.js viewer handles zooming?

Thanks and appreciate the work put into this library.

Steps to reproduce

Attached a codesandbox link showing this behavior.

https://codesandbox.io/p/sandbox/react-pdf-prevent-flash-with-scale-forked-pwcw8z

Expected behavior

Flickering for all pages is supposed to be minimal to none

Actual behavior

The first page doesn't flicker but the subsequent pages do.

Additional information

No response

Environment

jblack020 commented 1 month ago

Has a solution been posted for this? Would love to see a working fix for rendering the whole pdf without flashes. @wojtekmaj

gfargo commented 1 month ago

Having a similar issue, I've tried all of the provided solutions regarding hiding the canvas while the PDF is rendering e.g. #1340 & #1279 however these do not have any impact on the canvas disappearing when "zooming" the document.

The demo PDFJS does not have this issue and unfortunately this flicker is creating a horrible UIX when zooming/panning around a single page PDF.

From what I can tell, this wasn't an issue when using renderMode="svg" however that has been deprecated in the newer versions 😕 any guidance from others would be greatly appreciated!

jblack020 commented 1 month ago

This solution worked for me, albeit I have no idea why since this was quite long ago on a project I've since stopped working on. I'm quite new to React in general, but perhaps this may be of use.

My main PDFView component:

export const PDFView = ({ pdfURL }) => {
  const [numPages, setNumPages] = useState(null)

  // Config variables
  const initialWidth = 405 
  const stepSize = 0.5
  const magnifierMax = 4

  const { scale, newScale, pageVisible, scaleUp, scaleDown, scaleReset, togglePageVisibility } =
    usePDFScaling(initialWidth, stepSize, magnifierMax)

  useElectronAPI(scaleUp, scaleDown, scaleReset)

  return (
    <Document file={pdfURL} onLoadSuccess={({ numPages }) => setNumPages(numPages)}>
      {numPages &&
        Array.from(new Array(numPages), (_el, index) => (
          <React.Fragment key={`container_${index + 1}`}>
            <Page
              className={`mb-2 ${pageVisible ? 'visible' : 'hidden'}`}
              key={`front_page_${index + 1}`}
              pageNumber={index + 1}
              scale={pageVisible ? scale : newScale}
              width={initialWidth}
              onRenderSuccess={pageVisible ? null : togglePageVisibility}
            />
            <Page
              className={`mb-2 ${pageVisible ? 'hidden' : 'visible'}`}
              key={`back_page_${index + 1}`}
              pageNumber={index + 1}
              scale={pageVisible ? newScale : scale}
              width={initialWidth}
              onRenderSuccess={pageVisible ? togglePageVisibility : null}
            />
          </React.Fragment>
        ))}
    </Document>
  )
}

useElectronAPI hook:

export const useElectronAPI = (scaleUp, scaleDown, scaleReset) => {
  useEffect(() => {
    const handleZoom = (_event, action) => {
      switch (action) {
        case 'zoom-in':
          scaleUp()
          break
        case 'zoom-out':
          scaleDown()
          break
        case 'zoom-reset':
          scaleReset()
          break
      }
    }
    window.electronAPI.onMenuAction(handleZoom)

    return () => {
      window.electronAPI.removeMenuActionListener(handleZoom)
    }
  }, [scaleUp, scaleDown, scaleReset])
}

usePDFScaling hook:

export const usePDFScaling = (initialWidth, stepSize, magnifierMax) => {
  const [scale, setScale] = useState(1.0)
  const [newScale, setNewScale] = useState(1.0)
  const [pageVisible, setPageVisible] = useState(true)

  const minWidth = initialWidth / magnifierMax
  const maxWidth = initialWidth * magnifierMax

  const scaleUp = () => {
    setNewScale((prevScale) => Math.min(prevScale + stepSize, maxWidth / initialWidth))
  }

  const scaleDown = () => {
    setNewScale((prevScale) => Math.max(prevScale - stepSize, minWidth / initialWidth))
  }

  const scaleReset = () => {
    const windowWidth = window.innerWidth
    const resetScale = (windowWidth * 0.45) / initialWidth
    setNewScale(resetScale)
    setScale(resetScale)
  }

  useEffect(() => {
    scaleReset()
  }, [])

  const togglePageVisibility = () => {
    if (newScale !== scale) {
      setPageVisible(!pageVisible)
      setScale(newScale)
    }
  }

  return { scale, newScale, pageVisible, scaleUp, scaleDown, scaleReset, togglePageVisibility }
}
ShashanKV98 commented 1 month ago

Thanks. But it seems to have the same issue. I switched the electron API part with buttons in react for scaling. I am beginning to think that since page.render re creates from scratch, pages with heavy graphics tend to flicker or take time to render. I wonder if it's possible to just render the portion of the pdf in the viewport and not all the pages with every scale change or maybe utilizing an offscreencanvas, but I'm not sure how to go about it.

wojtekmaj commented 1 month ago

Duplicate of #1705 Duplicate of #875 Duplicate of #418

ShashanKV98 commented 1 month ago

Hi @wojtekmaj I've seen those issues but I just can't get the performance to be even close to how pdfjs does it (seems like it only renders the text part) . The conditional rendering does a decent job but it is not robust for bigger files. Can you please maybe point me in the direction of how to handle this or if you have any ideas I might try?