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.32k stars 879 forks source link

How to set 100% width? #129

Closed shamanth7077 closed 6 years ago

shamanth7077 commented 6 years ago

Is there a way to set 100% width to the pdf so that it occupies the full width of the container? I can see width property takes input only in pixels but not in percentage.

lucasveigaf commented 6 years ago

This fork has a fillWidth feature that does exactly that. Looking at the src code, it doesn't look too complex to implement it.

wojtekmaj commented 6 years ago

The solution in the fork seems okay. Just mind it just scales PDF once, not sure if that's desired.

It would be entirely possible in this repo., only I'd do this outside <Document>. What I would do is I'd create a wrapper component, set its width to desired using CSS, measure it using JavaScript and provide the result to the child Document component. Then, make sure to watch for window resize to update the measurements (don't forget to throttle!).

I currently have no plans of implementing width prop other than value in pixels. Other values would be quite problematic and supporting all possible cases would probably result in untestable mess.

lucasveigaf commented 6 years ago

Hey @wojtekmaj I'm interested in this aswell since I'll need to implement it soon. Your suggestion seems more appropriate. Do you mean something like this?

import React, { PureComponent } from "react"
import { Document, Page } from "react-pdf/build/entry.webpack"
import throttle from "lodash.throttle"
import pdf from "./pdf.pdf"

class App extends PureComponent {
  constructor(props) {
    super(props)
    this.state = {width: null}
  }

  componentDidMount () {
    this.setDivSize()
    window.addEventListener("resize", throttle(this.setDivSize, 500))
  }

  componentWillUnmount () {
    window.removeEventListener("resize", throttle(this.setDivSize, 500))
  }

  setDivSize = () => {
    this.setState({width: this.pdfWrapper.getBoundingClientRect().width})
  }

  render() {
    return (
      <div id="row" style={{height: "100vh", width: "100vw", display: "flex", overflow: "hidden"}}>
        <div id="placeholderWrapper" style={{width: "10vw", height: "100vh"}}/>
        <div id="pdfWrapper" style={{width: "90vw"}} ref={(ref) => this.pdfWrapper = ref}>
          <PdfComponent wrapperDivSize={this.state.width} />
        </div>
      </div>
    )
  }
}

class PdfComponent extends PureComponent {
  render() {
    return (
      <div>
        <Document
          file={pdf}
        >
          <Page pageIndex={1} width={this.props.wrapperDivSize} />
        </Document>
      </div>
    )
  }
}

export default App
wojtekmaj commented 6 years ago

Precisely what I meant @lucasveigaf! Good job :)

One thing - on componentWillUnmount you wanted to call removeEventListener, I suppose. Other than that, cool!

lucasveigaf commented 6 years ago

Thanks, @wojtekmaj, Oh yes, ofc, I'll update it. Great job on the component, btw.

67726e commented 5 years ago

For the intrepid developer stumbling across this issue, there is a bug in the provided solution:

componentDidMount () {
    this.setDivSize()
    window.addEventListener("resize", throttle(this.setDivSize, 500))
  }

  componentWillUnmount () {
    window.removeEventListener("resize", throttle(this.setDivSize, 500))
  }

The use of Lodash's throttle here is a problem as it returns a new function. Since removeEventListener removes the given function based on reference, you'll never actually remove your listener here as you're always adding a new, untracked function reference.

See the attached screenshots for examples.

As a solution, one could assign a method with the results from _.throttle, e.g.

handleResize = _.throttle(() => {
  // Code goes here
}, someTimeValue);

image image

kivervinicius commented 5 years ago

in gist file he has updated this. https://raw.githubusercontent.com/lucasveigaf/react-pdf-example/master/src/App.js

ghost commented 5 years ago

Evening all

I agree with @wojtekmaj comments and place this outside the Document:

It would be entirely possible in this repo., only I'd do this outside

The approach I have taken to solve this problem, is to leverage the library react-sizeme

And creating a wrapper around the document as follows:

<SizeMe
  monitorHeight
  refreshRate={128}
  refreshMode={"debounce"}
  render={({ size }) => (
    <div>
      <Document
          file={pdf_data_uri}
          onLoadSuccess={this.onDocumentLoadSuccess}
          onLoadError={this.onDocumentLoadError}
      >
        <div className={classes.pageBorder}>
          <Page width={size.width} pageNumber={pageNumber} />
        </div>
      </Document>
    </div>
  )}
/>

This will allow for the resizing both vertically and horizontally.

Thanks

Keaton

wvoelcker commented 5 years ago

I've just seen this. Wonder why not do it with CSS? Like this:

const PDFDocumentWrapper = styled.div`
  canvas {
    width: 100% !important;
    height: auto !important;
  }
`;
        <PDFDocumentWrapper>
          <Document
            file={`/etc...`}
          >
            <Page pageNumber={1} />

If not using styled-components, then you could obviously target the element using a className instead.

john-athena commented 5 years ago
import AutoSizer from 'react-virtualized/AutoSizer';

_renderPdf = () => {
    const {
      blob,
      pdfNumPages,
    } = this.props.cstore;
    return (
      <AutoSizer disableHeight>
        {({width}) => (
          <Document file={blob} onLoadSuccess={this._handlePdfLoaded}>
            {[...Array(pdfNumPages).keys()].map((i) => (
              <Page key={i} pageNumber={i + 1} width={width} />
            ))}
          </Document>
        )}
      </AutoSizer>
    );
  }
slyncio commented 4 years ago

I've just seen this. Wonder why not do it with CSS? Like this:

const PDFDocumentWrapper = styled.div`
  canvas {
    width: 100% !important;
    height: auto !important;
  }
`;
        <PDFDocumentWrapper>
          <Document
            file={`/etc...`}
          >
            <Page pageNumber={1} />

If not using styled-components, then you could obviously target the element using a className instead.

This option works in a purely css option. But it does not scale the pdf losing text quality and more.

haseebanwar commented 4 years ago

Came up with the following solution

import React, { Component } from 'react';
import throttle from 'lodash.throttle';

class MakePDFResponsive extends Component {
  constructor(props) {
    super(props)
    this.state = {
      PDFWidth: null
    }
    this.myInput = React.createRef()
  }

  componentDidMount() {
    // setting width at initial
    this.setPDFWidth()

    // event listener when window is resized
    window.addEventListener('resize', throttle(this.setPDFWidth, 500))
  }

  componentWillUnmount() {
    window.removeEventListener('resize', throttle(this.setPDFWidth, 500))
  }

  setPDFWidth = () => {
    const width = this.myInput.current.offsetWidth
    this.setState({ PDFWidth: width })
  }

render() {
    const { PDFWidth } = this.state
    return (
        <div ref={this.myInput}>
          <Document file='test.pdf'>
            <Page pageNumber={1} width={PDFWidth} />
          </Document>
        </div>
    )
  }
}
wojtekmaj commented 4 years ago

@Haseeb99 your removeEventListener won't work because

throttle(this.setPDFWidth, 500) === throttle(this.setPDFWidth, 500) // false

I suggest the following modfication:

+  throttledSetPDFWidth = throttle(this.setPDFWidth, 500);

  componentDidMount() {
    // setting width at initial
    this.setPDFWidth()

    // event listener when window is resized
-    window.addEventListener('resize', throttle(this.setPDFWidth, 500))
+    window.addEventListener('resize', this.throttledSetPDFWidth);
  }

  componentWillUnmount() {
-   window.removeEventListener('resize', throttle(this.setPDFWidth, 500))
+   window.removeEventListener('resize', this.throttledSetPDFWidth)
  }
haseebanwar commented 4 years ago

Got it Thank you

gunner963 commented 4 years ago

@lucasveigaf @Haseeb99 hey guys do your solutions keep the pdf quality intact on different devices ? I was just trying this solution by inspecting it for different devices in chrome ...pdf scaled flawlessly but I saw quality degraded as if nothing was visible on smaller width devices ... while I have not tried it on actual devices as I will have to make build for them and stuff so was just asking if it works just fine ? answer would be appreciated Thanks ! P.S. attached is the screenshot for mobile screen image

igorskiter commented 4 years ago

To force the height to 100% add

.react-pdf__Page__canvas {
  min-height: 100vh ! important;
  max-width: 100vw! important;
}

renderMode is the same as canvas, if you have svg look at the wrapper class.

wojtekmaj commented 4 years ago

@igorskiter Merged your comments into one.

Your solutions scales PDF so that it fits the page, but doesn't take into account that it may be blurred, because it wasn't rendered in desired size. Unfortunately React-PDF must know the size of the page to render. It can derive it from the PDF itself, but if you want the page to scale to fit the screen, this is unlikely what you want.

FurkanToprak commented 4 years ago

An improvement to @keatonvictor 's solution, using react-sizeme:

<SizeMe>
  {({ size }) => (
    <Document file={filename}>
      <Page pageNumber={1} width={size.width ? size.width : 1} />
    </Document>
  )}
</SizeMe>
ScalletLeng commented 4 years ago
 width = {document.getElementById('root').clientWidth}   

  <Document
          file={fileurl}
          onLoadSuccess={this.onDocumentLoadSuccess}
          onLoadError={console.error}
        >
        {Array.from(
        new Array(numPages),
        (el, index) => (
          <Page
          width = {document.getElementById('root').clientWidth}
            key={`page_${index + 1}`}
            pageNumber={index + 1}
          />
        ),
        )}
        </Document>
pianomansam commented 3 years ago

In reading this issue, the solution is neither clear nor does it seem to be fixed.

wojtekmaj commented 3 years ago

This is what I use on React-PDF demo page:

import { useWindowWidth } from '@wojtekmaj/react-hooks';

export default function Component() {
  const width = useWindowWidth();

  return (
    <Document file={…}>
      <Page
        pageNumber={…}
        width={Math.min(width * 0.9, 400)} // width: 90vw; max-width: 400px
      />
    </Document>
  );
}
pianomansam commented 3 years ago

Thanks @wojtekmaj. My point is that there are many solutions suggested in this thread, and quite a few of them are bad suggestions. Unless someone reads the entire thread, it's hard to know what solutions are good. There doesn't seem to be the "accepted solution" like you would have on Stack Overflow. The consensus seems to be that setting the width of the viewer is done by an outside method/library. I'm fine with that as long as it's clear how to do so. So my suggestion would be to document the "accepted solution" as well as clean this thread up so that it's apparent that this feature is not handled internally.

wojtekmaj commented 3 years ago

Only the fact that width must be passed explicitly in pixels is my concern. There isn't going to be an official solution of any sort.q

And for the lack of accepted solution... If people are using GitHub Issues as support forum which clearly isn't one you'll see this issue over and over.

I'm trying to help every individual calling for help but I never guarantee it's going to work for everyone. Especially with a package as high level as React-PDF.

daweimau commented 3 years ago

This is what I use on React-PDF demo page:

import { useWindowWidth } from '@wojtekmaj/react-hooks';

export default function Component() {
  const width = useWindowWidth();

  return (
    <Document file={…}>
      <Page
        pageNumber={…}
        width={Math.min(width * 0.9, 400)} // width: 90vw; max-width: 400px
      />
    </Document>
  );
}

This relies on window width but does not regard parent element size, which does not necessarily correspond to window width

wojtekmaj commented 2 years ago

@andrewmcoupe That is a hack and will cause excessive misalignments between visual and text layers. This is my official recommendation: https://github.com/wojtekmaj/react-pdf/issues/129#issuecomment-810438957

andrewmcoupe commented 2 years ago

@andrewmcoupe That is a hack and will cause excessive misalignments between visual and text layers. This is my official recommendation: #129 (comment)

Thanks @wojtekmaj

Kentakoong commented 2 years ago

How about retaining it into the div? could we possibly do that, because my PDF would be dynamically loaded in. And I wouldn't know the exact size, so if I just fixed the width to 100%, the height would overflow, if it's an A4 size, for example.

XcrossD commented 2 years ago

This is another implementation using https://www.npmjs.com/package/@react-hook/resize-observer. Resizing vertically is also possible, check out the quick start example on the page.

import { useRef, useState, useLayoutEffect } from 'react';
import useResizeObserver from '@react-hook/resize-observer';

const useWidth = (target) => {
  const [width, setWidth] = useState(null);

  useLayoutEffect(() => {
    setWidth(target.current.getBoundingClientRect().width)
  }, [target]);

  useResizeObserver(target, (entry) => setWidth(entry.contentRect.width));
  return width;
};

export default function Component() {
  const wrapperDiv = useRef(null);
  const width = useWidth(wrapperDiv);

  return (
    <div className="wrapper" ref={wrapperDiv}>
      <Document file={…}>
        <Page
          pageNumber={…}
          width={width}
        />
      </Document>
    </div>
  );
}
dharmveer97 commented 1 year ago
const pdfViewerRef = useRef(null);

  useEffect(() => {
    const pdfViewer = pdfViewerRef.current;
    const scale = Math.min(
      pdfViewer.clientHeight / pdfViewer.scrollHeight,
      pdfViewer.clientWidth / pdfViewer.scrollWidth
    );
    pdfViewer.scale = scale;
  }, []);
DariusLukasukas commented 10 months ago
import ReactResizeDetector from "react-resize-detector";

 <ReactResizeDetector handleWidth handleHeight>
            {({ width, height, targetRef }) => (
              <div ref={targetRef as React.LegacyRef<HTMLDivElement>}>
                <Document file="/pdfs/x.pdf" options={options}>
                  <Page pageNumber={1} width={width} height={height} />
                </Document>
              </div>
            )}
 </ReactResizeDetector>
jsoconno commented 6 months ago

I was able to get this working for me as a standalone component using Shadcn UI for the sheet:

import React, { useState, useEffect, useRef } from 'react';
import { Document, Page, pdfjs } from 'react-pdf';
import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
import { Sheet, SheetContent } from "@/components/ui/sheet";
import useResizeObserver from '@react-hook/resize-observer'

pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.js`;

export const PDFViewer = ({ documentUrl, open, setOpen }) => {
  const [numPages, setNumPages] = useState(null);
  const sheetContentRef = useRef(null);
  const [pageWidth, setPageWidth] = useState(0);

  const onDocumentLoadSuccess = ({ numPages }) => {
    setNumPages(numPages);
  };

  const handleResize = (entry) => {
    const { width } = entry.contentRect;
    const margin = 20; // Adjust this margin as necessary
    setPageWidth(width - margin);
  };

  useResizeObserver(sheetContentRef, handleResize);

  return (
    <Sheet open={open} onOpenChange={setOpen}>
      <SheetContent ref={sheetContentRef} className="overflow-auto max-h-[100vh]">
        <Document file={documentUrl} onLoadSuccess={onDocumentLoadSuccess}>
          {Array.from(new Array(numPages), (el, index) => (
            <Page key={`page_${index + 1}`} pageNumber={index + 1} width={pageWidth} />
          ))}
        </Document>
      </SheetContent>
    </Sheet>
  );
};