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.
The component takes a File object straight from an input.
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.
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.
data-pdf-thumbnail-srcAdd 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.
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.
|
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. |
| 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. |
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);
});
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 }.
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>
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 });
}
});
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>
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;
});
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. |
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');
});
<img> a meaningful alt, naming the document the
thumbnail is of. The library never sets one, since only the calling code knows what the file is.
File is revoked. The data URL is all that remains, so it is worth keeping tiles
small, which is why the default format is JPEG.
getThumbnail() resolve with undefined when a later
load(), reset() or destroy() replaces the document while
the thumbnail is being drawn, matching how load() behaves.
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).
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.
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.
The library is wrapped in UMD, so it works as a global (window.PdfPreview), via
CommonJS (require('pdf-preview')) or with AMD.