PDF Preview - standalone example

No project CSS, no project bundles and no jQuery. The only script on this page is pdf-preview.min.js, a self-contained UMD library with no dependencies (pdf.js is fetched from a CDN on first use). Text and controls in the previews are browser defaults; the preview CSS in the head only tints the gutter and adds a shadow to the pages, so you can see what the library gives you and what is decoration.

1. Preview a PDF from your device

The component takes a File object straight from an input.

/

2. Preview a PDF from a URL

Add data-pdf-preview-src and a single PdfPreview.initAll() call picks it up and loads it. Options can be set declaratively on the root element.

/

3. Thumbnail of the first page

A thumbnail is one page drawn to an offscreen canvas and handed back as a data URL, ready for the src of an <img>. It needs no preview and no root element. The tile on the left is filled from markup alone; pick a file to fill the second one.

First page of lorem_ipsum.pdf
From markup: data-pdf-thumbnail-src
From a file on your device.

Documentation

Getting started

Add a root element with a pages container inside it, create a preview for the root and load a document into it. Every control is optional; leave out the ones you do not need.

<div id="preview" data-pdf-preview>
  <button type="button" data-pdf-preview-prev>Prev</button>
  <input type="number" data-pdf-preview-page> / <span data-pdf-preview-total></span>
  <button type="button" data-pdf-preview-next>Next</button>
  <div data-pdf-preview-pages></div>
</div>

<script src="pdf-preview.min.js"></script>
<script>
  var preview = new PdfPreview('#preview', { height: '70vh' });

  preview.load(file); // a File, a Blob or a URL
</script>

To set everything up from markup instead, put the options on the root as data- attributes (see Options) and call PdfPreview.initAll() once. A root with data-pdf-preview-src starts loading straight away.

Options

Pass options as the second argument of new PdfPreview() or PdfPreview.initAll(), or as data- attributes on the root element. When both are given, the JavaScript value wins. Options are read once, when the preview is created.

Option Attribute Type Default Description
src data-pdf-preview-src string none URL of a PDF to load as soon as the preview is created. Same as calling load(url).
fitWidth data-pdf-preview-fit-width boolean true Sizes every page to the width of the pages container. false draws pages at the PDF's own size multiplied by scale, never wider than the container. As an attribute, only "false" turns it off.
scale data-pdf-preview-scale number 1.5 Page size relative to the PDF's own size. Used when fitWidth is false, or when the container has no width at load time.
maxPixelRatio data-pdf-preview-max-pixel-ratio number 2 Upper limit on the screen density pages are drawn at. A higher value is sharper on dense screens but uses more memory. See Sharpness.
height data-pdf-preview-height number | string '50vh' Maximum height of the pages container, which scrolls once the pages are taller. Any CSS length; bare numbers are px. 'none' removes the limit, so the pages flow with the page around them. Set as --pdfp-height.
spacing data-pdf-preview-spacing number | string 16px Gap between pages and padding around them. Any CSS length; bare numbers are px. Give the container a background colour to make the gutter visible. Set as --pdfp-spacing.
pageClass data-pdf-preview-page-class string '' Extra class added to every page canvas, for your own styling. Separate several classes with spaces.
injectStyles - boolean true Adds the base layout styles and the pdfp- classes. false leaves all layout to your CSS. See Styling.
pdfjsLib - object none pdf.js instance to use instead of fetching one. A window.pdfjsLib already on the page is used the same way. Set its GlobalWorkerOptions.workerSrc yourself.
pdfjsUrl - string pdf.js 3.11.174 on cdnjs Script fetched when no pdf.js is available. It is fetched once per page, by the first preview that loads a document.
pdfjsWorkerUrl - string pdf.js 3.11.174 worker on cdnjs Worker script for the pdf.js fetched from pdfjsUrl.
selectors - object see Markup hooks Replaces any of the markup hook selectors for this preview, for example { next: '.js-next' }. Keys you leave out keep their defaults.

Markup hooks

The library finds its elements with these selectors, inside the root. Everything except the root is optional. Rename any of them with the selectors option, so another project does not have to use these attribute names.

Key Default selector Element Description
root [data-pdf-preview] any Wraps the preview and its controls. initAll() looks for it; when you create a preview yourself you pass the element, so no attribute is needed.
pages [data-pdf-preview-pages] any Container the pages are drawn into, and the one that scrolls. Falls back to the root when missing.
first [data-pdf-preview-first] button Goes to the first page.
prev [data-pdf-preview-prev] button Goes to the previous page.
next [data-pdf-preview-next] button Goes to the next page.
last [data-pdf-preview-last] button Goes to the last page.
pageInput [data-pdf-preview-page] input Shows the current page and follows scrolling. Entering a number goes to that page; an out-of-range number is put back to the current page.
pageTotal [data-pdf-preview-total] any Its text is set to the number of pages.

Methods

Method Returns Description
new PdfPreview(target, options) preview Creates a preview on target: a selector, an element or a jQuery object. If the element already has a preview, that one is returned and options are ignored. Throws when the element is not found.
load(source) Promise Shows a PDF from a File, a Blob or a URL, replacing any document already shown. Resolves with the pdf.js document once every page is drawn, or with undefined when a later load(), reset() or destroy() cancels it. Rejects when the PDF cannot be shown.
reset() - Cancels any load in progress, removes the pages and frees the document. The preview stays usable.
goToPage(page) preview Scrolls to page page, counting from 1. Page numbers out of range are ignored.
getCurrentPage() number Page closest to the middle of the view. 1 when nothing is loaded.
getPageCount() number Number of pages in the document. 0 when nothing is loaded.
destroy() - Resets the preview, removes its listeners and detaches it from the element. Your markup is left in place, so a new preview can be created on it.
getThumbnail(options) Promise Thumbnail of the document this preview already has open, without reading the file again. See Thumbnails.
PdfPreview.get(target) preview | null The preview already created on target, or null.
PdfPreview.initAll(scope, options) array Creates a preview for every root inside scope (default: the whole document), passing options to each. Returns the previews.
PdfPreview.thumbnail(source, options) Promise Thumbnail of one page of a File, Blob or URL, as a data URL. No preview and no element needed. See Thumbnails.
PdfPreview.thumbnailInto(target, source, options) Promise The same, rendered straight into an <img>. See Thumbnails.
PdfPreview.initThumbnails(scope, options) array Fills every [data-pdf-thumbnail] inside scope (default: the whole document) from its data-pdf-thumbnail-src. Returns a promise per element. Elements already handled are skipped.
PdfPreview.defaults object Default options. Changing a value before previews are created changes it for all of them.
PdfPreview.thumbnailDefaults object Default thumbnail options. Changing a value changes it for every thumbnail drawn afterwards.
PdfPreview.selectors object Default markup hook selectors. Changing a value before previews are created changes it for all of them.
PdfPreview.classes object Names of the classes the library adds, for reference. Do not change them.

Events

Dispatched on the root element. All of them bubble, so you can also listen higher up.

Event Detail Description
pdfpreview:loaded { pageCount } The document has opened and its page count is known. Pages are drawn after this; the promise from load() resolves once they are all drawn.
pdfpreview:pagechange { page } The current page changed, by scrolling or through the controls. Currently only fired when the preview has a pageInput element.
pdfpreview:error { error } The PDF could not be shown: the file is damaged or password-protected, or pdf.js failed to load. Not fired for loads that were cancelled.
document.querySelector('#local-preview')
  .addEventListener('pdfpreview:loaded', function (e) {
    console.log('pages:', e.detail.pageCount);
  });

Thumbnails

A thumbnail is one page of a PDF (the first, unless you ask for another) drawn to an offscreen canvas and handed back as a data URL. The canvas is thrown away, so what you hold on to is a string you can put in an <img>, store, or pass around. There is no pagination, no scrolling container and no root element: a thumbnail is a still image, and sits anywhere an image does.

Thumbnails resolve with the data URL and the size to show it at, in CSS px: { dataUrl, width, height, page, pageCount }.

Fill an <img> yourself

PdfPreview.thumbnail() takes the same sources as load(): a File, a Blob or a URL.

<img id="thumb" alt="First page of the document">

<script src="pdf-preview.min.js"></script>
<script>
  var img = document.getElementById('thumb');

  PdfPreview.thumbnail('invoice.pdf', { width: 120 })
    .then(function (thumb) {
      img.src = thumb.dataUrl;
      img.width = thumb.width;   // keeps the tile from jumping
      img.height = thumb.height;
    })
    .catch(function (error) {
      console.warn('no thumbnail:', error);  // leave the fallback icon in place
    });
</script>

Straight from a file input

PdfPreview.thumbnailInto() does the same and sets src, width and height on the element for you. On failure it leaves the element alone, so whatever it already shows stays visible.

document.getElementById('file').addEventListener('change', function (event) {
  var file = event.target.files[0];

  if (file && file.type === 'application/pdf') {
    PdfPreview.thumbnailInto('#thumb', file, { width: 120 });
  }
});

From markup

Put the source and any options on the <img> as data- attributes and call PdfPreview.initThumbnails() once, the same way PdfPreview.initAll() works for previews. Calling it again picks up newly added elements and skips the ones already done, so it is safe after rendering more markup.

<img
  data-pdf-thumbnail
  data-pdf-thumbnail-src="invoice.pdf"
  data-pdf-thumbnail-width="120"
  alt="First page of invoice.pdf"
>

<script>PdfPreview.initThumbnails();</script>

Alongside a preview

When a preview already has the document open, ask it for the thumbnail instead. The file is read and parsed once and both come out of the same document, which is the cheapest option when you show a tile and a full preview of the same upload.

var preview = new PdfPreview('#preview');

preview.load(file).then(function () {
  return preview.getThumbnail({ width: 120 });
}).then(function (thumb) {
  document.getElementById('thumb').src = thumb.dataUrl;
});

Thumbnail options

Pass these as the last argument of thumbnail(), thumbnailInto(), initThumbnails() or getThumbnail(), or as data- attributes on the element. When both are given, the JavaScript value wins.

Option Attribute Type Default Description
src data-pdf-thumbnail-src string none URL of the PDF. Used by initThumbnails(), and by thumbnailInto() when no source is passed.
width data-pdf-thumbnail-width number 160 Width in CSS px to draw the page at. The height follows from the page's aspect ratio.
height data-pdf-thumbnail-height number none Height in CSS px. On its own it drives the size; with width the page is fitted inside the box, so a landscape page comes out shorter than the box rather than stretched.
page data-pdf-thumbnail-page number 1 Page to draw, counting from 1. Numbers past the end are clamped to the last page.
type data-pdf-thumbnail-type string 'image/jpeg' Image format of the data URL. 'image/png' is sharper on small text and much larger; anything the browser's canvas.toDataURL() supports works.
quality data-pdf-thumbnail-quality number 0.85 Quality from 0 to 1, for lossy formats. Ignored for PNG.
background - string '#ffffff' Colour drawn behind the page. A PDF page carries no background of its own, so this is what shows through its margins.
maxPixelRatio - number 2 As for previews: the limit on the screen density the page is drawn at. See Sharpness.
pdfjsLib, pdfjsUrl, pdfjsWorkerUrl - object | string as above Same meaning as for previews, and the same pdf.js: whichever of a preview or a thumbnail runs first fetches it, and the rest reuse it.

Thumbnail events

Dispatched on the <img> by thumbnailInto() and initThumbnails(), so markup-driven thumbnails can be followed without holding on to a promise. Both bubble.

Event Detail Description
pdfthumbnail:loaded { dataUrl, width, height, page, pageCount } The thumbnail is drawn and the element's src has been set.
pdfthumbnail:error { error } The thumbnail could not be drawn: the file is damaged or password-protected, the URL could not be fetched, or pdf.js failed to load. The element is left as it was.
document.addEventListener('pdfthumbnail:error', function (e) {
  e.target.closest('.upload').classList.add('upload--no-thumbnail');
});

Notes

Styling

The library brings layout only: a scrolling pages container with the pages stacked inside it. Everything visual is up to you. The base styles are added at the very top of <head>, so any rule in your own stylesheet wins over them. To change the height or spacing from CSS, set the real property (max-height, padding, margin-top) rather than the custom property, which the options set on the element itself.

Hook Set on Description
.pdfp-pages pages container Scrolling, maximum height and padding. Only added when injectStyles is on.
.pdfp-page each page canvas Stacks the pages and sets the gap between them. Only added when injectStyles is on.
.pdfp-page--auto each page canvas Natural-size pages, when fitWidth is false. Only added when injectStyles is on.
data-page each page canvas The page number, from 1. Always set.
--pdfp-height pages container Value of the height option.
--pdfp-spacing pages container Value of the spacing option. Only set when the option is given; the base styles fall back to 16px.
--pdfp-page-width each page canvas The width the page is meant to be shown at. The base styles use it for natural-size pages; use it yourself when injectStyles is off.

The custom properties are set even when injectStyles is off, so your own CSS can use them, for example max-height: var(--pdfp-height).

Sharpness

Pages are drawn at the size they are shown multiplied by the device pixel ratio (capped by maxPixelRatio), so they stay sharp on dense screens without wasting memory on others. Very large pages are scaled down to stay within the largest canvas iOS Safari will draw.

The container width is measured when a document loads, so load into a visible container. In a modal, call load() once the modal is shown. A container with no width falls back to scale. Pages are not redrawn when the container is resized; the layout stays correct, but they may look slightly soft until the next load.

Security

Every document is opened with pdf.js's isEvalSupported set to false, for thumbnails as much as for previews. With it on (the pdf.js default), pdf.js turns font glyphs into code with new Function(), and in versions before 4.2.67 a crafted PDF can use that to run its own script on the page showing it (CVE-2024-4367). With it off, glyphs are interpreted instead, which is slightly slower. The setting is passed each time a document is opened, so it also covers a pdf.js supplied through pdfjsLib. Treat this as a safeguard, not a substitute for keeping pdf.js up to date, especially where staff open PDFs uploaded by customers.

Module formats

The library is wrapped in UMD, so it works as a global (window.PdfPreview), via CommonJS (require('pdf-preview')) or with AMD.