npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-fast-scroll-pdf

v1.3.1

Published

Image-based PDF viewer using pdfjs

Downloads

429

Readme

react-fast-scroll-pdf

A small, super-fast javascript PDF renderer

We needed a way to very quickly render a complete PDF document but without using a lot of canvas elements (iPads etc limit the amount of canvas memory). This uses a single canvas element and uses pdfjs-dist to render each pdf page before converting it to a PNG image. PDF annotations are then placed on top of the image so that links etc still work.

[screenshot)


Getting Started

To get a local copy up and running follow these simple steps.

Prerequisites

Install via npm

npm install --save react-fast-scroll-pdf

Or clone locally

git clone https://github.com/wearemothership/react-fast-scroll-pdf

Demo

We have provided some demos of how this can be used in your project.

Online demo

Or build and run the demos locally
git clone https://github.com/wearemothership/react-fast-scroll-pdf
cd react-fast-scroll-pdf
npm install
npm run example

Usage

Some usage examples of how this can be used in you project.

Simple

import the FastScrollPDF component and pass in the required props

import React, { SyntheticEvent, useState } from "react";
import { FastScrollPDF } from "react-fast-scroll-pdf";

const App = (): JSX.Element => {
	const [file, setFile] = useState<Uint8Array>();
	const sourceOptions = {
		data: file
	};

	const fileChanged = (ev: SyntheticEvent<HTMLInputElement>) => {
		const target = ev.target as HTMLInputElement;
		const newFile = target.files?.[0];
		if (newFile) {
			const fileReader = new FileReader();

			fileReader.onload = (e) => {
				const { result } = e.target as FileReader;
				const arr = new Uint8Array(result as ArrayBuffer);
				setFile(arr);
			};
			fileReader.readAsArrayBuffer(newFile);
		}
	};

	return (
		<div className="App">
			<input type="file" onChange={fileChanged} />
			{ file ? <FastScrollPDF source={sourceOptions} /> : null }
		</div>
	);
};

export default App;

Medium

This is something that you may wish to do if you want to apply your own styles or get access to the individual components to sort layout out in your own way. Here you're going to use the components along with the hook to give you lots of control over how you display the PDF.

Note: For reloading the PDF efficiently you should pass in refs to the PDFDocument component and then use those refs for changeZoom. If you choose not to use the refs, it will all still work but instead of reloading from the currently active page, it will reload from page 1.

import React, { SyntheticEvent, useState } from "react";
import { ZoomButtons, PDFDocument, usePDF } from "react-fast-scroll-pdf";

const App = (): JSX.Element => {
	const [file, setFile] = useState<Uint8Array>();
	const sourceOptions = {
		data: file
	};
	const scrollContainerRef = useRef();
	const viewerRef = useRef();
	const {
		pages,
		changeZoomStart,
		changeZoomEnd,
		renderCurrentPage
	} = usePDF({
			source,
			scrollContainer: scrollContainerRef.current,
			viewer: viewerRef.current
		});
	
	// Recommend debouncing this code, e.g. lodash debounce.
	const scrollDocument = () => renderCurrentPage();

	const fileChanged = (ev: SyntheticEvent<HTMLInputElement>) => {
		const target = ev.target as HTMLInputElement;
		const newFile = target.files?.[0];
		if (newFile) {
			const fileReader = new FileReader();

			fileReader.onload = (e) => {
				const { result } = e.target as FileReader;
				const arr = new Uint8Array(result as ArrayBuffer);
				setFile(arr);
			};
			fileReader.readAsArrayBuffer(newFile);
		}
	};

	const zoomChangeStart = useCallback((newZoom: number) => {
		// This causes the pages to zoom in, but in low quality.
		changeZoomStart(newZoom);
	}, [changeZoomStart]);

	const zoomChangeEnd = useCallback(() => {
		// This tells the renderer to recreate all of the images.
		changeZoomEnd();
	}, [changeZoomEnd])

	// Allows the page to be zoomed to fit to the page width
	// This is OPTIONAL
	const fitPage = useCallback(() => {
		if (viewportWidth && scrollContainerRef.current) {
			const initial = viewportWidth / zoom;
			const scale = (scrollContainerRef.current.offsetWidth / initial) * 0.95;
			doZoom(scale);
			changeZoomEnd();
			return scale;
		}
		return undefined;
	}, [viewportWidth, doZoom, changeZoomEnd, zoom]);

	useEffect(() => {
		const oldRef = scrollContainerRef.current;
		scrollContainerRef.current?.addEventListener("scroll", scrollDocument);

		return () => oldRef?.removeEventListener("scroll", scrollDocument);
	}, [scrollDocument]);

	return (
		<div className="App">
			<input type="file" onChange={fileChanged} />
			<ZoomButtons zoomChangeStart={zoomChangeStart} zoomChangeEnd={zoomChangeEnd} zoomFit={fitPage} />
			<PDFDocument scrollContainerRef={scrollContainerRef} viewerRef={viewerRef} pages={pages} />
		</div>
	);
};

export default App;

Hard

You can import the usePDF hook element into React as below (see Example above to see this running)

import React, { SyntheticEvent, useRef, useState } from 'react';
import { usePDF } from "react-fast-scroll-pdf";

const App = (): JSX.Element => {
  	const scrollContainerRef = useRef<HTMLDivElement>(null);
	const viewerRef = useRef<HTMLDivElement>(null);
	const [zoom, setZoom] = useState<number>(1);
	const [file, setFile] = useState<Uint8Array>();
	const sourceOptions = {
		data: file
	}
	const {
		pages,
		changeZoomStart,
		changeZoomEnd,
		renderCurrentPage
	} = usePDF({
		source: sourceOptions,
		scrollContainer: scrollContainerRef.current,
		viewer: viewerRef.current
	})
	const ZOOM_CHANGE: number = 0.1;

	// Recommend debouncing this, e.g. lodash debounce.
	const scrollDocument = () => renderCurrentPage();

	const zoomIn = useCallback(() => {
		const newZoom = zoom + ZOOM_CHANGE;
		if (newZoom > 0) {
			setZoom(newZoom);
			changeZoomStart(newStart);
		}
	}, [changeZoomStart]);

	const zoomOut = useCallback(() => {
		const newZoom = zoom - ZOOM_CHANGE;
		if (newZoom < 2) {
			setZoom(newZoom);
			changeZoomStart(newZoom);
		}
	}, [changeZoomStart]);

	const zoomEnd = useCallback(() => {
		changeZoomEnd();
	}, [changeZoomEnd]);

	const fileChanged = useCallback((ev: SyntheticEvent<HTMLInputElement>) => {
		const target = ev.target as HTMLInputElement;
		const file = target.files?.[0]
		if (file) {
			var fileReader = new FileReader();  

			fileReader.onload = (e) => {
				const { result } = e.target as FileReader;
				const arr = new Uint8Array(result as ArrayBuffer);
				setFile(arr);
			};
			fileReader.readAsArrayBuffer(file);
		}
	}, [])

	return (
		<div className="App">
			<input type="file" onChange={fileChanged} />
			<div id="scrollContainer" ref={scrollContainerRef} style={{ overflow: "scroll", height: "800px" }}>
				<div id="viewer" ref={viewerRef}>
					{pages}
				</div>
			</div>
			<button type="button" onMouseDown={zoomIn} onMouseUp={zoomEnd}>+</button>
			<button type="button" onMouseDown={zoomOut} onMouseUp={zoomEnd}>-</button>
		</div>
	);
}

PDFPage and PlaceholderPage

These two components can be accessed via an import and may be useful if you wish to add additional pages to the page list returned by the usePDF hook.

Props

FastScrollPDF

  • source: (required) a PDFJS source object (see below)
  • loadingImage: (optional) an image that will be spun in the middle of loading pages (default: spinner gif - courtesy of icons8).
  • spinLoadingImage: (optional) Whether to spin the loading image (default: false)
  • enableAnnotations: (optional) whether to create an annotations layer. (default: true),
  • className: (optional) a CSS class to apply to the main window. (default: none),
  • hideZoom: (optional) if true, the zoom buttons are hidden. (default: false),
  • showFitPage: (optional) if true, the Fit to Page button is shown. (default: false)

PDFDocument

  • pages: (required) The list of pages returned from the usePDF hook.
  • scrollContainerRef: (optional) a ref object to store a ref to the div set to be used for scrolling.
  • viewerRef: (optional) a ref object to store a ref to the div which contains all the pages (usually a child of scrollContainer).
  • className: (optional) a class to apply to the document div.
  • rowGap: (optional) the gap between pages as a css string (default: "16px").

ZoomButtons

  • zoomChange: (required) a function that accepts a zoom number and carried out the required zoom action.
  • zoomStep: (optional) the increment to change the zoom amount by per second (plus or minus). (default: 1)
  • zoomStart: (optional) the starting zoom. (default: 1)
  • zoomFit: (optional) the function called that should calculate and return the new scaling factor.
  • minZoom: (optional) the minimum amount of zoom (default: 0.1)
  • maxZoom: (optional) the maximum amount of zoom (default: 5)
  • buttonClasses: (optional) a CSS class to apply to the buttons.
  • groupClasses: (optional) a CSS class to apply to the div that groups the buttons.
  • selectedClass: (optional) a CSS class to apply to selected buttons.
  • icons: (optional) an object of icons/text to display on each button: { zoomIn: ReactNode, zoomOut: ReactNode, fitPage: ReactNode }

PDFPage

  • width: (required) the width of the page in px.
  • height: (required) the height of the page in px.
  • pageNum: (required) the page number represented by the component.
  • imageSrc: (required) a data URL or image location to be displayed.
  • type: (optional) one of "place" or "canvas" for placeholder or image canvas.
  • children: (optional) child nodes to display over the top of the page (e.g. annotation layer - use styling to position correctly)

PlaceholderPage

  • width: (required) the width of the page in px.
  • height: (required) the height of the page in px.
  • type: (optional) one of "place" or "canvas" for placeholder or image canvas.
  • loadingImage: (optional) an image src string to display in the centre of the loading page (png, jpeg, gif etc);
  • spin: (optional) a boolean whether to spin the image (default: false)

usePDF

  • source: (required) a PDFJS source object (see below)
  • loadingImage: (optional) an image that will be spun in the middle of loading pages (default: Font Awesome Spinner).
  • spinLoadingImage: (optional) Whether to spin the loading image (default: false)
  • enableAnnotations: (optional) whether to create an annotations layer. (default: true)
  • scrollContainer: (optional) Reference to the HTMLDivElement used as a scroll container for the viewer
  • viewerContainer: (optional) Reference to the HTMLDivElement used as the viewer.

usePDF return

  • pages: a fragment of Placeholder pages or PDFPages (as images) as div & img elements
  • changeZoom: call this function to zoom in on the pdf and recreate the images. (scale: number) => void
  • changeZoomStart: Call this function when zooming first starts. (scale: number) => void
  • changeZoomEnd: call this function when zooming ends. () => void
  • renderCurrentPage: Render the page currently in view (and the next page). If the page is already rendered nothing happens unless 'force' is true. (force: boolean) => void
  • viewportWidth: (number) The current width of the viewport (a page)
  • viewportHeight: (number) The current height of the viewport (a page)

PDF.js source object

You can find details of the options available on the source object here.


Roadmap

See the open issues for a list of proposed features (and known issues).


Contributing

Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are greatly appreciated.

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

License

Distributed under the MIT License. https://github.com/wearemothership/react-fast-scroll-pdf/blob/main/LICENSE.md

Copyright (c) 2021 Mothership Software Ltd.


react-fast-scroll-pdf is used in…

Please let us know if you wish us to add your project to this list.


Made by Mothership

wearemothership.com