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.

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>

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 in-app.
E-learning
Course handouts inside the lesson page.
Review tools
A base for annotation and approval flows.
Internal dashboards
Preview generated PDFs before sending.
Learning PDF.js
The essential API in one file.
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.