---
title: "How to add PDF page navigation, zoom, and rotation controls with PDF.js"
canonical_url: "https://www.nutrient.io/blog/pdfjs-navigation-zoom-rotation/"
md_url: "https://www.nutrient.io/blog/pdfjs-navigation-zoom-rotation.md"
last_updated: "2026-08-11T08:57:05.236Z"
description: "Learn how to build a toolbar for PDF.js with page navigation, zoom controls, and rotation. This tutorial covers PDFViewer APIs, EventBus listeners, and a complete React toolbar component."
---

**TL;DR**

Build a PDF.js toolbar with navigation, zoom, and rotation by operating on the `PDFViewer` instance and subscribing to `EventBus` events:

- **Page navigation** — `viewer.currentPageNumber`, `previousPage()`, `nextPage()`, listen for `pagechanging`

- **Zoom** — Assign string values (`"auto"`, `"page-width"`, `"1.5"`) to `viewer.currentScaleValue`, listen for `scalechanging`

- **Rotation** — Increment `viewer.pagesRotation` in 90-degree increments, listen for `rotationchanging`

- **Layout refresh** — Call `viewer.update()` on panel resizes

- **Precise scroll-to-position** — Use `PDFLinkService.goToDestination` with the `XYZ` destination type

If you’d rather skip the wiring, [Nutrient Web SDK](https://www.nutrient.io/sdk/web-overview/) ships a built-in toolbar and a typed `ViewState` API.

Building a toolbar for your PDF viewer means wiring up controls for page navigation, zoom, and rotation. All three operate through the `PDFViewer` instance and the `EventBus`.

## Page navigation

The `PDFViewer` instance tracks the current page and exposes methods to move between pages, while the `EventBus` reports page changes back to your UI.

### Reading current page

```tsx

const currentPage = viewer.currentPageNumber; // 1-indexed.
const totalPages = pdfDocument.numPages;

```

### Changing pages

```tsx

// Go to a specific page.
viewer.currentPageNumber = 5;

// Previous/Next.
viewer.previousPage();
viewer.nextPage();

```

### Listening for page changes

```tsx

eventBus.on("pagechanging", (evt) => {
  setCurrentPage(evt.pageNumber);
});

```

### Navigating to a specific location

Use `PDFLinkService.goToDestination` to scroll to an exact position on a page:

```tsx

linkService.goToDestination([
  pageIndex,         // 0-indexed page number.
  { name: "XYZ" },  // destination type.
  scrollLeft,        // x position (PDF coordinates), or null for current.
  scrollTop,         // y position (PDF coordinates), or null for current.
  null,              // zoom level, or null for current.
]);

```

This is the same mechanism PDF internal links use to scroll to a target location.

### React page navigation component

The component below assumes a `PDFContext` that exposes `viewer`, `eventBus`, and `pdfDocument` as references (typically populated when the PDF.js `PDFViewer` is initialized in a parent component).

```tsx

import { useContext, useEffect, useState } from "react";
import { PDFContext } from "./PDFContext";

function PageToolbar() {
  const { viewer, eventBus, pdfDocument } = useContext(PDFContext);
  const [currentPage, setCurrentPage] = useState(1);
  const [totalPages, setTotalPages] = useState(0);

  useEffect(() => {
    if (pdfDocument.current) {
      setTotalPages(pdfDocument.current.numPages);
    }
  }, [pdfDocument]);

  useEffect(() => {
    const handler = (evt) => setCurrentPage(evt.pageNumber);
    eventBus.current?.on("pagechanging", handler);
    return () => eventBus.current?.off("pagechanging", handler);
  }, [eventBus]);

  return (
    <div>
      <button
        onClick={() => viewer.current?.previousPage()}
        disabled={currentPage <= 1}
      >
        Previous
      </button>
      <input
        type="number"
        value={currentPage}
        min={1}
        max={totalPages}
        onChange={(e) => {
          const page = parseInt(e.target.value, 10);
          if (page >= 1 && page <= totalPages) {
            viewer.current.currentPageNumber = page;
          }
        }}
      />
      <span>of {totalPages}</span>
      <button
        onClick={() => viewer.current?.nextPage()}
        disabled={currentPage >= totalPages}
      >
        Next
      </button>
    </div>
  );
}

```

## Zoom/scale

Zoom is controlled through `viewer.currentScaleValue`, which accepts both preset keywords and numeric values, and the `EventBus` emits a `scalechanging` event whenever the scale updates.

### Setting scale

```tsx

// Preset values (strings).
viewer.currentScaleValue = "auto";       // Fit to container.
viewer.currentScaleValue = "page-width"; // Fit width.
viewer.currentScaleValue = "page-fit";   // Fit entire page.

// Numeric values (as string).
viewer.currentScaleValue = "1.5";        // 150 percent zoom.
viewer.currentScaleValue = "0.75";       // 75 percent zoom.

```

### Zoom in/out

```tsx

function zoomIn(viewer) {
  const current = parseFloat(viewer.currentScaleValue) || 1;
  viewer.currentScaleValue = String(Math.min(current + 0.25, 5));
}

function zoomOut(viewer) {
  const current = parseFloat(viewer.currentScaleValue) || 1;
  viewer.currentScaleValue = String(Math.max(current - 0.25, 0.25));
}

```

### Listening for scale changes

```tsx

eventBus.on("scalechanging", (evt) => {
  setScale(evt.scale);       // numeric value.
  setPreset(evt.presetValue); // "auto", "page-width", etc.
});

```

## Rotation

The `viewer.pagesRotation` property holds the current rotation, which you change in 90-degree increments. Each change adds to the previous value, and the `EventBus` fires a `rotationchanging` event whenever the rotation updates.

### Rotating pages

```tsx

// Rotate 90 degrees clockwise.
viewer.pagesRotation += 90;

// Rotate 90 degrees counterclockwise.
viewer.pagesRotation -= 90;

```

`pagesRotation` is cumulative. Values are in degrees (0, 90, 180, 270).

### Listening for rotation changes

```tsx

eventBus.on("rotationchanging", (evt) => {
  setRotation(evt.pagesRotation);
});

```

## Force refresh on layout changes

If your PDF viewer is in a resizable panel (sidebar toggle, split view), the pages may not reflow automatically. Force a refresh by calling `viewer.update()`:

```tsx

function refreshViewer() {
  viewer.update();
}

// Call on panel resize, sidebar toggle, etc.
window.addEventListener("resize", refreshViewer);
// Or use a custom event.
window.addEventListener("refresh-pdf-viewer", refreshViewer);

```

## Combined toolbar example

```tsx

import { useContext, useEffect, useState } from "react";
import { PDFContext } from "./PDFContext";

function PDFToolbar() {
  const { viewer, eventBus, pdfDocument } = useContext(PDFContext);
  const [page, setPage] = useState(1);
  const [total, setTotal] = useState(0);

  useEffect(() => {
    setTotal(pdfDocument.current?.numPages || 0);
    const h = (e) => setPage(e.pageNumber);
    eventBus.current?.on("pagechanging", h);
    return () => eventBus.current?.off("pagechanging", h);
  }, [eventBus, pdfDocument]);

  return (
    <div className="pdf-toolbar">
      {/* Page Navigation */}
      <button onClick={() => viewer.current?.previousPage()}>Prev</button>
      <span>{page} / {total}</span>
      <button onClick={() => viewer.current?.nextPage()}>Next</button>

      {/* Zoom */}
      <button onClick={() => viewer.current && zoomOut(viewer.current)}>-</button>
      <button onClick={() => {
        if (viewer.current) viewer.current.currentScaleValue = "page-width";
      }}>Fit Width</button>
      <button onClick={() => viewer.current && zoomIn(viewer.current)}>+</button>

      {/* Rotation */}
      <button onClick={() => {
        if (viewer.current) viewer.current.pagesRotation -= 90;
      }}>Rotate Left</button>
      <button onClick={() => {
        if (viewer.current) viewer.current.pagesRotation += 90;
      }}>Rotate Right</button>
    </div>
  );
}

```

## Key points

- Page numbers are 1-indexed in `viewer.currentPageNumber` but 0-indexed in `goToDestination`

- Scale values are strings — even numeric values like `"1.5"`

- Rotation accumulates in degrees on `viewer.pagesRotation`

- Always listen to `EventBus` events to keep your UI in sync with the viewer state

- Call `viewer.update()` to force a layout refresh after panel resizes

- `goToDestination` with `"XYZ"` type gives you precise scroll-to-position control

## FAQ

#### Why is <code>viewer.currentPageNumber</code> 1-indexed but <code>goToDestination</code> 0-indexed?

`currentPageNumber` is the user-facing page number (matches “Page 1 of 10” in the UI). `goToDestination` takes the array form from the PDF specification, where the first element is a zero-based page index. The two indexing conventions are intentional but easy to mix up.

#### Why are scale values strings instead of numbers?

`viewer.currentScaleValue` accepts both preset keywords (`"auto"`, `"page-width"`, `"page-fit"`) and numeric zoom levels (`"1.5"` for 150 percent) as strings. Using a single string type lets PDF.js distinguish “fit-to-width” from a hardcoded zoom. Always pass strings — `viewer.currentScaleValue = 1.5` silently fails.

#### How do I force the viewer to rerender after a layout change?

Call `viewer.update()`. This remeasures the container and reflows the pages. Common triggers: sidebar toggles, split-pane resizes, fullscreen transitions, and dynamic CSS changes that affect the container’s dimensions.

#### Do I need to import PDF.js’s CSS for the toolbar to work?

You need `pdfjs-dist/web/pdf_viewer.css` for the text layer, annotation layer, and page layout. The toolbar is your own React component, so it uses your own styles. Without the PDF.js CSS, pages render, but text selection and annotations break.

#### How does Nutrient Web SDK compare for the same use case?

Nutrient ships a built-in toolbar with navigation, zoom, rotation, and search controls, plus a typed `ViewState` API for programmatic control. There’s no `EventBus`, no string-typed scale values, and no manual layout refresh. See the [migration guide](https://www.nutrient.io/guides/web/about/migration-guides/migrating-from-mozilla-pdfjs.md) for switching from PDF.js.

## How Nutrient Web SDK handles this

Instead of wiring up `EventBus` listeners, parsing string-typed scale values, and manually refreshing layouts, Nutrient’s `ViewState` API handles navigation, zoom, and rotation declaratively:

```js

// Page navigation.
instance.setViewState((v) => v.set("currentPageIndex", 4));

// Zoom.
instance.setViewState((v) => v.set("zoom", "FIT_WIDTH"));

// Rotation.
instance.setViewState((v) =>
  v.set("pagesRotation", (v.pagesRotation + 90) % 360),
);

```

The built-in toolbar ships with navigation controls, and `ViewState` keeps the toolbar, thumbnails, and scroll position in sync automatically.

---

_See [Nutrient Web SDK](https://www.nutrient.io/sdk/web-overview/) for an alternative to PDF.js, or follow the [migration guide](https://www.nutrient.io/guides/web/about/migration-guides/migrating-from-mozilla-pdfjs.md) to switch. [Talk to Sales](https://www.nutrient.io/contact-sales/?=sdk) about your requirements._
---

## Related pages

- [The business case for accessibility: Five ways it drives enterprise value](/blog/5-ways-accessibility-drives-enterprise-value.md)
- [Accessibility Untangled Why It Matters Guide](/blog/accessibility-untangled-why-it-matters-guide.md)
- [Advanced Techniques For React Native Ui Components](/blog/advanced-techniques-for-react-native-ui-components.md)
- [`vector_store` holds your indexed documents (see the multimodal RAG post](/blog/agentic-rag.md)
- [Ai Document Automation Extraction To Action](/blog/ai-document-automation-extraction-to-action.md)
- [Ai Legal Assistant Document Authoring](/blog/ai-legal-assistant-document-authoring.md)
- [Angular File Viewer Pdf Image Office Files](/blog/angular-file-viewer-pdf-image-office-files.md)
- [Auto Tagging And Document Accessibility In Dotnet Sdk](/blog/auto-tagging-and-document-accessibility-in-dotnet-sdk.md)
- [Best Document Ai Platforms](/blog/best-document-ai-platforms.md)
- [Best Document Viewers](/blog/best-document-viewers.md)
- [The CEO’s AI playbook: Why decision architecture beats model selection](/blog/ceo-ai-playbook-decision-architecture.md)
- [1. Extract and chunk the PDF.](/blog/chat-with-pdf.md)
- [Complete Guide To Pdfjs](/blog/complete-guide-to-pdfjs.md)
- [Construction Document Data Extraction](/blog/construction-document-data-extraction.md)
- [Convert One Drive Files To Pdf In Sharepoint](/blog/convert-one-drive-files-to-pdf-in-sharepoint.md)
- [Create And Edit Pdfs In Flutter](/blog/create-and-edit-pdfs-in-flutter.md)
- [Create Pdfs With React](/blog/create-pdfs-with-react.md)
- [Creating A Document Scanner With Ocr In Python](/blog/creating-a-document-scanner-with-ocr-in-python.md)
- [Creating And Filling Pdf Forms Programmatically In Javascript](/blog/creating-and-filling-pdf-forms-programmatically-in-javascript.md)
- [The CTO’s AI playbook: Why accountability architecture beats orchestration](/blog/cto-ai-playbook-accountability-architecture.md)
- [Digital Signatures](/blog/digital-signatures.md)
- [Digital Workflow Automation](/blog/digital-workflow-automation.md)
- [Document Ai Vs Ocr](/blog/document-ai-vs-ocr.md)
- [Document Extraction Confidence Scores](/blog/document-extraction-confidence-scores.md)
- [Document Viewer](/blog/document-viewer.md)
- [Document Watermarking](/blog/document-watermarking.md)
- [Emerging threats: Your logging system may be an agentic threat vector](/blog/emerging-threats-your-logging-system.md)
- [Extract Patient Data On Premises](/blog/extract-patient-data-on-premises.md)
- [app.py](/blog/extract-text-from-pdf-using-python.md)
- [Fillable Pdf](/blog/fillable-pdf.md)
- [How To Add Digital Signature To Pdf Using React](/blog/how-to-add-digital-signature-to-pdf-using-react.md)
- [How To Build A Dotnet Maui Pdf Viewer](/blog/how-to-build-a-dotnet-maui-pdf-viewer.md)
- [How To Build A Flutter Pdf Viewer](/blog/how-to-build-a-flutter-pdf-viewer.md)
- [or](/blog/how-to-build-a-javascript-pdf-viewer-with-pdfjs.md)
- [How To Build A Javascript Pdf Viewer](/blog/how-to-build-a-javascript-pdf-viewer.md)
- [or](/blog/how-to-build-a-nextjs-pdf-viewer.md)
- [How To Build A Powerpoint Viewer Using Javascript](/blog/how-to-build-a-powerpoint-viewer-using-javascript.md)
- [Using Yarn](/blog/how-to-build-a-react-excel-viewer.md)
- [How To Build A React Native Pdf Viewer](/blog/how-to-build-a-react-native-pdf-viewer.md)
- [How To Build A React Powerpoint Viewer](/blog/how-to-build-a-react-powerpoint-viewer.md)
- [How To Build A Reactjs File Viewer](/blog/how-to-build-a-reactjs-file-viewer.md)
- [or](/blog/how-to-build-a-reactjs-pdf-viewer-with-react-pdf.md)
- [or](/blog/how-to-build-a-reactjs-pdf-viewer.md)
- [How To Build A Reactjs Viewer With Pdfjs](/blog/how-to-build-a-reactjs-viewer-with-pdfjs.md)
- [How To Build A Vuejs Pdf Viewer With Pdfjs](/blog/how-to-build-a-vuejs-pdf-viewer-with-pdfjs.md)
- [How To Build A Vuejs Pdf Viewer](/blog/how-to-build-a-vuejs-pdf-viewer.md)
- [How To Build An Android Pdf Viewer](/blog/how-to-build-an-android-pdf-viewer.md)
- [How To Build An Angular Pdf Viewer With Ng2 Pdf Viewer](/blog/how-to-build-an-angular-pdf-viewer-with-ng2-pdf-viewer.md)
- [How To Build An Angular Pdf Viewer With Pdfjs](/blog/how-to-build-an-angular-pdf-viewer-with-pdfjs.md)
- [How To Convert Docx To Pdf Using Javascript](/blog/how-to-convert-docx-to-pdf-using-javascript.md)
- [How To Convert Docx To Pdf Using Python](/blog/how-to-convert-docx-to-pdf-using-python.md)
- [How To Convert Html To Pdf Using Html2pdf](/blog/how-to-convert-html-to-pdf-using-html2pdf.md)
- [or](/blog/how-to-convert-html-to-pdf-using-react.md)
- [How To Convert Html To Pdf Using Wkhtmltopdf And Csharp](/blog/how-to-convert-html-to-pdf-using-wkhtmltopdf-and-csharp.md)
- [or](/blog/how-to-convert-html-to-pdf-using-wkhtmltopdf-and-python.md)
- [How To Convert Word To Pdf In Nodejs](/blog/how-to-convert-word-to-pdf-in-nodejs.md)
- [or](/blog/how-to-create-a-react-js-signature-pad.md)
- [How To Create Pdfs With React To Pdf](/blog/how-to-create-pdfs-with-react-to-pdf.md)
- [How To Edit Pdfs Using Ios Pdf Library](/blog/how-to-edit-pdfs-using-ios-pdf-library.md)
- [How To Embed A Pdf Viewer In Your Website](/blog/how-to-embed-a-pdf-viewer-in-your-website.md)
- [How To Extract Tables From Pdf And Images](/blog/how-to-extract-tables-from-pdf-and-images.md)
- [How To Generate Pdf From Html With Nodejs](/blog/how-to-generate-pdf-from-html-with-nodejs.md)
- [base_url tells WeasyPrint where to resolve relative asset paths](/blog/how-to-generate-pdf-reports-from-html-in-python.md)
- [How To Merge Pdfs Using Javascript](/blog/how-to-merge-pdfs-using-javascript.md)
- [How To Ocr Pdfs In Linux](/blog/how-to-ocr-pdfs-in-linux.md)
- [How To Print Pdf In Csharp](/blog/how-to-print-pdf-in-csharp.md)
- [Open an image.](/blog/how-to-use-tesseract-ocr-in-python.md)
- [From an HTML string.](/blog/html-in-pdf-format.md)
- [Javascript Pdf Editors](/blog/javascript-pdf-editors.md)
- [Javascript Pdf Libraries](/blog/javascript-pdf-libraries.md)
- [Linearized Pdf](/blog/linearized-pdf.md)
- [or](/blog/merge-pdfs.md)
- [Swift Package Manager](/blog/mobile-pdf-sdk.md)
- [`elements` come from your document parser — each has a type and content.](/blog/multimodal-rag.md)
- [Nutrient Vs Conga Composer](/blog/nutrient-vs-conga-composer.md)
- [Online Document Viewer](/blog/online-document-viewer.md)
- [Open Pdf In Your Web App](/blog/open-pdf-in-your-web-app.md)
- [Building WCAG 2.2, Section 508, and PDF/UA-compliant PDFs with an SDK](/blog/pdf-accessibility.md)
- [Pdf Data Extraction Developer Guide](/blog/pdf-data-extraction-developer-guide.md)
- [Pdf Extraction Benchmark Opendataloader Bench](/blog/pdf-extraction-benchmark-opendataloader-bench.md)
- [Pdf Extraction Document Case Studies](/blog/pdf-extraction-document-case-studies.md)
- [Pdf Page Labels](/blog/pdf-page-labels.md)
- [Pdf Sdk Compliance Security Checklist](/blog/pdf-sdk-compliance-security-checklist.md)
- [Pdf Sdk Performance Benchmark](/blog/pdf-sdk-performance-benchmark.md)
- [Pdf Ua Compliance Guide](/blog/pdf-ua-compliance-guide.md)
- [Pdfjs Accessibility Structtree Printing](/blog/pdfjs-accessibility-structtree-printing.md)
- [Pdfjs Advanced Loading Streaming Workers](/blog/pdfjs-advanced-loading-streaming-workers.md)
- [Pdfjs Annotation Editor Layer](/blog/pdfjs-annotation-editor-layer.md)
- [Pdfjs Area Annotations Canvas Capture](/blog/pdfjs-area-annotations-canvas-capture.md)
- [Pdfjs Coordinate Systems Pdf To Screen](/blog/pdfjs-coordinate-systems-pdf-to-screen.md)
- [Pdfjs Document Outline Bookmarks Metadata](/blog/pdfjs-document-outline-bookmarks-metadata.md)
- [Pdfjs Eventbus Guide](/blog/pdfjs-eventbus-guide.md)
- [macOS](/blog/pdfjs-file-format-conversion-to-pdf.md)
- [macOS](/blog/pdfjs-generating-pdf-thumbnails-pdf2pic.md)
- [Pdfjs Limitations Commercial Upgrade](/blog/pdfjs-limitations-commercial-upgrade.md)
- [Pdfjs Native Annotation Layer Forms](/blog/pdfjs-native-annotation-layer-forms.md)
- [Pdfjs Pdf Page Manipulation Pdf Lib](/blog/pdfjs-pdf-page-manipulation-pdf-lib.md)
- [Pdfjs React Viewer Setup](/blog/pdfjs-react-viewer-setup.md)
- [Pdfjs Rendering Overlays React Portals](/blog/pdfjs-rendering-overlays-react-portals.md)
- [Pdfjs Server Side Text Extraction](/blog/pdfjs-server-side-text-extraction.md)
- [Pdfjs Sticky Note Annotations](/blog/pdfjs-sticky-note-annotations.md)
- [Pdfjs Text Highlight Annotations](/blog/pdfjs-text-highlight-annotations.md)
- [Pdfjs Text Search Pdffindcontroller](/blog/pdfjs-text-search-pdffindcontroller.md)
- [Pdfjs Thumbnail Sidebar](/blog/pdfjs-thumbnail-sidebar.md)
- [Process Flows](/blog/process-flows.md)
- [React Native Pdf Annotation](/blog/react-native-pdf-annotation.md)
- [Using Yarn](/blog/react-pdf-editor.md)
- [or](/blog/sample-blog-updated.md)
- [Sdk Product Updates Q2 2026](/blog/sdk-product-updates-q2-2026.md)
- [Add DWS MCP Server to your Claude Code project.](/blog/teaching-llms-to-read-pdfs.md)
- [Open an image file.](/blog/tesseract-python-guide.md)
- [Define the HTML part of the document.](/blog/top-10-ways-to-generate-pdfs-in-python.md)
- [Top 5 Javascript Pdf Viewers](/blog/top-5-javascript-pdf-viewers.md)
- [or](/blog/top-js-pdf-libraries.md)
- [Convert an HTML file to PDF.](/blog/top-ten-ways-to-convert-html-to-pdf.md)
- [Vector Pdf](/blog/vector-pdf.md)
- [Wcag2 Accessibility Requirements Documents](/blog/wcag2-accessibility-requirements-documents.md)
- [Web Sdk Is Now Headless](/blog/web-sdk-is-now-headless.md)
- [What Are Annotations](/blog/what-are-annotations.md)
- [What Is A Vpat](/blog/what-is-a-vpat.md)
- [What Is Document Processing](/blog/what-is-document-processing.md)
- [What Is Intelligent Document Processing](/blog/what-is-intelligent-document-processing.md)
- [What Is Pdf Ua](/blog/what-is-pdf-ua.md)
- [Why Pdfium Is A Trusted Platform For Pdf Rendering](/blog/why-pdfium-is-a-trusted-platform-for-pdf-rendering.md)
- [Why Your Ai Agent Hallucinates Pdf Table Data](/blog/why-your-ai-agent-hallucinates-pdf-table-data.md)

