PDF.js Viewer with Thumbnails, Zoom and Rotate — Free JavaScript Snippet

PDF.js Viewer with Thumbnails, Zoom and Rotate · Media · Plain HTML, CSS & JS · Live preview

What's included

Features

PDF.js rendering
Mozilla's engine, no native viewer.
Thumbnail sidebar
Click to jump; current page highlighted.
Page navigation
Buttons, page input and keyboard.
Zoom presets and fit width
Rotation-aware fitting.
Rotation
90° steps via viewport rotation.
High-DPI output
Rendered at devicePixelRatio.
Render cancellation
No garbled pages from overlapping renders.
Local file opening
Private, with error handling.

About this UI Snippet

Build Your Own PDF Viewer With PDF.js — Thumbnails, Zoom and Rotation

Screenshot of the PDF.js Viewer with Thumbnails, Zoom and Rotate snippet rendered live

Browsers can show PDFs natively, but an embedded native viewer can't be styled, controlled or integrated with the rest of your UI. PDF.js — the engine behind Firefox's built-in viewer — gives you the rendering and leaves the interface to you. This snippet builds a compact viewer with the controls people expect.

Load, get a page, render to canvas

pdfjsLib.getDocument({ data }) parses the file and resolves to a document. getPage(n) returns a page, getViewport({ scale, rotation }) computes its size and transform, and page.render({ canvasContext, viewport }) draws it onto a canvas. Everything else in a viewer is state around those calls: current page, zoom level and rotation.

Sharp on high-DPI screens

The canvas is rendered at scale × devicePixelRatio and then shrunk back with CSS width and height, so text stays crisp on Retina and 4K displays instead of looking blurry.

Fit width, including rotated pages

Fit width divides the available stage width by the page's width at scale 1 — measured *with the current rotation*, because a rotated portrait page is as wide as it was tall. The sample includes a landscape page to show mixed orientations.

Cancel before re-rendering

Clicking zoom twice quickly would start two renders on the same canvas and produce a corrupted page. The snippet keeps the current renderTask, cancels it, waits for it to settle, then starts the new render; the RenderingCancelledException from a cancelled task is expected and ignored.

Thumbnails

Every page is also rendered into a small canvas in the sidebar. Clicking one jumps to it, and the current page is marked with aria-current="page" and scrolled into view.

Private file opening

Opening a local file reads it with the File API; nothing is uploaded. A generated five-page sample is shown on load.

Step by step

How to Use

  1. 1
    Browse the sampleA five-page document opens with thumbnails on the left.
  2. 2
    NavigateUse ‹ ›, type a page number, click a thumbnail, or arrow keys on the page.
  3. 3
    ZoomFit width, preset levels, or + and − (buttons or keys).
  4. 4
    Rotate⟳ turns the page 90° at a time; fit width adjusts.
  5. 5
    Open your own PDFIt's read locally and never uploaded.

Real-world uses

Common Use Cases

Document portals
Show invoices, contracts or reports inside your own interface, with a thumbnail sidebar that highlights the current page and jumps on click.
E-learning handouts
Display course materials in the lesson page itself, with page-number input, buttons and keyboard shortcuts for navigation.
Review and approval tools
Use as the base for annotation workflows, since PDF.js rendering leaves the surrounding controls entirely under your control.
Generated PDF previews
Preview a generated document before sending it, using rotation-aware fit-width zoom and high-DPI rendering for crisp text.
PDF.js API reference
Learn the essentials in one file, including safe render task handling so rapid page changes do not produce overlapping draws.
Related: PDF.js Text Search
Related: jsPDF Invoice Generator

Got questions?

Frequently Asked Questions

Load the document with pdfjsLib.getDocument({ url }) or ({ data }), get a page with getPage(n), create a viewport with getViewport({ scale }), size a canvas to the viewport and call page.render({ canvasContext, viewport }).

The canvas is being drawn at CSS pixel size on a high-DPI screen. Render at scale × window.devicePixelRatio and set the canvas's CSS width and height to the unscaled size.

Pass rotation (0, 90, 180 or 270) to getViewport. Remember that width and height swap at 90 and 270 degrees when calculating fit-to-width.

Two renders are drawing into the same canvas. Keep the render task, call cancel() on it before starting a new render, and ignore the RenderingCancelledException it rejects with.

PDF.js 3.x ships a classic pdf.min.js that defines window.pdfjsLib. Version 4 and later are ES modules, which you load with a module script or a bundler.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Paste this viewer into an AI assistant like Claude and ask it to explain render cancellation and device-pixel-ratio scaling. Ask it to add a continuous scroll mode that lazily renders pages as they enter view, a text layer for selection, printing, or pinch-to-zoom on touch devices. It can also help migrate the code to PDF.js 4's ES module build.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a PDF viewer with PDF.js 3 (classic build from a CDN, window.pdfjsLib) in plain HTML, CSS and JavaScript on a dark theme, using jsPDF only to generate a sample document.

Requirements:
- Generate a five-page sample PDF (one landscape page) with jsPDF and open it on load; also open local files from a file input, with an error message if a file can't be read.
- A toolbar with previous/next buttons, a page-number input with total, zoom out/in buttons, a zoom select (fit width and 50–200%), a rotate button and the file name.
- Render the current page to a canvas at scale × devicePixelRatio with CSS size set to the unscaled size, supporting rotation and a fit-width mode that accounts for rotation.
- Cancel any in-progress render task before starting a new one, ignoring the cancellation error.
- A sidebar of page thumbnails that jump to a page on click and mark the current page with aria-current, hidden on small screens.
- Keyboard shortcuts on the focused page area: arrows and Page Up/Down to navigate, + and − to zoom; re-fit on window resize.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Source Code

<div class="pv">
  <div class="pv-toolbar" role="toolbar" aria-label="PDF controls">
    <label class="pv-open">Open PDF<input type="file" id="pvFile" accept="application/pdf"></label>
    <span class="pv-sep"></span>
    <button type="button" id="pvPrev" aria-label="Previous page">‹</button>
    <label class="pv-page"><input id="pvNum" type="number" min="1" value="1" aria-label="Page number"> / <span id="pvCount">–</span></label>
    <button type="button" id="pvNext" aria-label="Next page">›</button>
    <span class="pv-sep"></span>
    <button type="button" id="pvOut" aria-label="Zoom out">−</button>
    <select id="pvZoom" aria-label="Zoom level">
      <option value="fit">Fit width</option><option value="0.5">50%</option><option value="0.75">75%</option>
      <option value="1">100%</option><option value="1.25">125%</option><option value="1.5">150%</option><option value="2">200%</option>
    </select>
    <button type="button" id="pvIn" aria-label="Zoom in">+</button>
    <button type="button" id="pvRot" aria-label="Rotate 90 degrees">⟳</button>
    <span class="pv-name" id="pvName"></span>
  </div>
  <div class="pv-body">
    <nav class="pv-thumbs" id="pvThumbs" aria-label="Page thumbnails"></nav>
    <div class="pv-stage" id="pvStage" tabindex="0" aria-label="Page view"><canvas id="pvCanvas"></canvas></div>
  </div>
</div>