Closed shamanth7077 closed 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.
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
Precisely what I meant @lucasveigaf! Good job :)
One thing - on componentWillUnmount you wanted to call removeEventListener
, I suppose. Other than that, cool!
Thanks, @wojtekmaj, Oh yes, ofc, I'll update it. Great job on the component, btw.
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);
in gist file he has updated this. https://raw.githubusercontent.com/lucasveigaf/react-pdf-example/master/src/App.js
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
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.
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>
);
}
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.
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>
)
}
}
@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)
}
Got it Thank you
@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
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.
@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.
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>
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>
In reading this issue, the solution is neither clear nor does it seem to be fixed.
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>
);
}
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.
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.
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
@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 That is a hack and will cause excessive misalignments between visual and text layers. This is my official recommendation: #129 (comment)
Thanks @wojtekmaj
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.
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>
);
}
const pdfViewerRef = useRef(null);
useEffect(() => {
const pdfViewer = pdfViewerRef.current;
const scale = Math.min(
pdfViewer.clientHeight / pdfViewer.scrollHeight,
pdfViewer.clientWidth / pdfViewer.scrollWidth
);
pdfViewer.scale = scale;
}, []);
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>
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>
);
};
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.