DEV Community

Cover image for What are PDF.js Layers and How You Can Use them in Vue.js
Kittisak Ma
Kittisak Ma

Posted on • Originally published at blog.vue-pdf-viewer.dev

What are PDF.js Layers and How You Can Use them in Vue.js

PDF is everywhere, no matter what kind of web app you’re building, you’re bound to need a convenient PDF viewer at some point. But if you’ve ever tried to handle PDFs on the web, you know it can feel like wrestling with outdated plugins, iframe workaround or settling for awkward pop-ups.

That’s where PDF.js comes in—it's an open-source JavaScript library for rendering PDFs right in your browser. Now, if you’re like me and build apps in Vue.js, figuring out how to use PDF.js in your project can be tricky at first.

To leverage PDF.js's capabilities, it's important to understand its layered architecture. If you’ve ever wondered, “How do I enable text selection?” or “How do I handle links inside the PDF?” or even “How do I build advanced features like annotations?”, the answer usually lies in understanding these layers.

In this article, I’ll show you how PDF.js creates layers, what each layer is responsible for, and how you can apply them to your next Vue project. Let’s get going!


Vue PDF Viewer: Flexible and Powerful Vue.js PDF Component

Quick heads up on something I’ve been working on: Vue PDF Viewer, a handy PDF Viewer that renders PDFs right within your Vue or Nuxt app. It packs over 20 features, including out-of-the-box default toolbar, customization and responsive design, so your users never have to leave your site to interact with your documents.

Vue PDF Viewer

If that sounds interesting, I’d love for you to give Vue PDF Viewer a try. Your support helps me keep creating awesome tools and tutorials like this one. ❤️


Overview of PDF.js Layers

Before we dive in, here’s a quick snapshot of the four major layers in PDF.js. Each layer handles a specific aspect of rendering or user interaction:

  1. Canvas Layer: Render the static visual content of your PDF (shapes, images, text-as-graphics). It is the foundation of the PDF Viewer.
  2. Text Layer: Sit on top of the canvas to ensure that text is selectable and searchable.
  3. Annotation Layer: Handle interactive elements (links, forms, highlights) so users can click, type, or navigate.
  4. Structural Layer: Manage the overall layout, including alignment and scaling for all the other layers.

By splitting different functionalities into distinct layers, PDF.js remains modular, efficient, and surprisingly easy to tweak.

PDF.js Layers


Let’s start by creating a fresh Vue.js project to integrate PDF.js.

Set up Vue.js Project

In this article, I use Codepen as a code editor. If you want to follow along, you can create a new Pen on Codepen and follow the steps below.

Step 1: Pick a Vue Pen Template

The easiest way to get started is by using CodePen’s built-in Vue template. From the left menu, click on the Pen menu to open the options, then pick Vue Pen.

Codepen - Vue Pen

This gives you a pre-configured environment with a default Vue setup. In this interface, you can experiment with Vue codes and instantly see your results in the preview area.

Codepen - first impression of Vue code editor

Step 2: Configure the project and install PDF.js

Next, let’s configure your Vue project to use the pdfjs-dist library.

  1. Open Settings: Click on the Settings button at the top of your Pen.
  2. Select JS Options: In the popup, switch to the JS tab.
  3. Pick Vue Version: Under Vue version, select Vue 3 (or whichever version fits your needs).
  4. Add Packages: In the Add Packages box, search for and add pdfjs-dist.
  5. Save and Close: Click Save & Close to apply your changes.

Codepen - how to set Vue version and install dependencies

CodePen will automatically inject an import statement for pdfjs-dist into your code. You’ll typically want to move that statement below your <script> section to keep things clean and organized.

That’s it! You’re now set up to start experimenting with PDF.js layers within a Vue environment. So let us get to the layers.


1. Canvas Layer

Purpose

The Canvas Layer is the foundation—it’s where PDF.js draws the visual elements such as images, shapes, and text (as graphics). Essentially, what you see on-screen is coming from this layer.

How It Works

  • PDF.js uses the HTML <canvas> element to display the PDF's visual content
  • It utilizes the browser's 2D canvas API for high-performance rendering.
  • This means your PDF content will look consistent and accurate across various screen sizes and devices.

Use Cases

  • Ideal for non-interactive or static PDF content.
  • It renders the document as an image, ensuring PDF’s fonts, colors, and layouts are consistent across all devices.

Example of Canvas layer Code with Vue

Here’s a quick CodePen demo showcasing a minimal Canvas Layer setup in Vue.js.

📜 How the Code Works

Below, I’ll walk through the major steps for rendering a PDF page onto the canvas.

(1) Setting Up the PDF Worker

Before loading the PDF, we need to configure the PDF worker. The worker is responsible for processing the PDF in a separate thread, improving performance.

import * as PDFJS from "https://esm.sh/pdfjs-dist";
import * as PDFWorker from "https://esm.sh/pdfjs-dist/build/pdf.worker.min";

try {
  PDFJS.GlobalWorkerOptions.workerSrc = PDFWorker;
} catch (e) {
  window.pdfjsWorker = PDFWorker;
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Here, we import pdf.worker.min.js and set it as the worker source.
  • The worker processes the PDF in a separate thread so our UI is not blocked.
  • If an error occurs, the worker is assigned to window.pdfjsWorker as a fallback

(2) Fetching and Loading the PDF

 const PDF_SRC = "https://pdfobject.com/pdf/pdf_open_parameters_acro8.pdf";
 export default {
  ...
  methods: {
    processLoadingTask(source) {
      const loadingTask = PDFJS.getDocument(source);
      loadingTask.promise
      .then(docProxy => ...)
      .then(page => {
         ...
         const renderContext = {
           canvasContext: context,
           viewport: viewport
         };
         return page.render(renderContext);
      })
    }
   },
   // Start the process when the component is mounted
   mounted() {
    this.processLoadingTask(PDF_SRC);
   }
 }
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • The processLoadingTask method initializes PDF loading via PDFJS.getDocument(source) from pdfjs-dist.
  • Once the PDF is fetched (from PDF_SRC), we store the document proxy (docProxy).
  • The total number of pages is determined and stored in totalPages.

(3) Rendering the Canvas Layer

Explanation:
After the PDF is loaded and we have a page object, the first page is extracted using getPage(1). PDF.js then:

  • Scales the canvas to match the PDF page dimensions.
  • A <canvas> element (canvasLayer) is used to render the PDF page as an image.
  • The rendering process is handled by page.render(renderContext), where canvasContext paints the page content onto the canvas.

This ensures the final output accurately matches the original PDF’s appearance.


2. Text Layer

Purpose

The Text Layer ensures that the text in your PDF is selectable, searchable, and accessible. While the Canvas Layer does the heavy lifting of rendering visuals, the Text Layer is what lets you highlight and copy actual text.

How It Works

  • PDF.js extracts the text content separately from the PDF.
  • It positions each piece of text exactly over the underlying canvas, using absolutely positioned <div> elements.
  • Even though you might not see these text <div> visually (they’re often transparent), they’re there to support selection and searching.

Use Cases

  • Enable text selection and copying from the PDF.
  • Text layer is crucial for accessibility, as it enables screen readers to interpret the content.
  • Provide text searching and highlighting within the PDF viewer.
  • Keep the text aligned with the rendered PDF page

Text layer Code Example

Here’s a Vue.js CodePen example that demonstrates a Text Layer in action:

📜 How the Code Works

(1) Structuring the Layers

<div ref="pdfLayersWrapper" class="pdf__layers">
  <div class="pdf__canvas-layer">
    <canvas ref="canvasLayer" />
  </div>
  <div ref="textLayer" class="pdf__text-layer"></div>
</div>
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • The pdfLayersWrapper is a container that holds both the Canvas Layer and the Text Layer.
  • The Canvas Layer (canvasLayer) displays the PDF page visually.
  • The Text Layer (textLayer) sits on top of the canvas, allowing text selection.

(2) Fetching and Loading the PDF

(Identical to the steps in the Canvas Layer—we load the PDF document first.)

(3) Rendering the Text Layer

renderText(pdfPageProxy, textLayerContainer, viewport) {
  ...
  pdfPageProxy
    .getTextContent()
    .then((content) => {
      const renderTask = new PDFJS.TextLayer({
        container: textLayerContainer,
        textContentSource: content,
        viewport: viewport.clone({ dontFlip: true })
      });
      return renderTask.render();
    });
 },
 processLoadingTask (source) {
  ...
  this.renderText(...)
 }
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Render the text layer (this.renderText()).
  • pdfPageProxy.getTextContent() retrieves the text data from the PDF page.
  • This data is passed into a new TextLayer, specifying:
    • container: Container where text is rendered.
    • textContentSource: Extract text data from the PDF.
    • viewport: Define text positioning and scaling.
  • renderTask.render() places invisible but selectable text <div> over the canvas.

3. Annotation Layer

Purpose

The Annotation Layer handles interactive elements like hyperlinks, highlights, form fields, and comments. If you need your PDF viewer to support internal linking (jumping to different pages), or if your PDFs have fillable forms, this layer is for you.

How It Works

  • PDF.js extracts annotation data (links, form fields, etc.) from the PDF.
  • It overlays these data as interactive HTML elements (e.g. <a>, <input>, <textarea>), which sit above the Canvas Layer and Text Layer positioned using CSS.
  • This layer ensures that users can click, type, and interact without altering the PDF’s static visuals.

Use Cases

  • Clicking on links within PDF to navigate between pages.
  • Rendering highlights, underlines, and notes from PDF annotations.
  • Displaying interactive form fields within a PDF.

Annotation layer Code Example

Check out this CodePen example on the Annotation Layer for Vue.js:

You can click the links inside the Codepen to change the PDF's page.

📜 How the Code Works

(1) Structuring the Layers

<div ref="pdfLayersWrapper" class="pdf__layers">
  <div class="pdf__canvas-layer">
    <canvas ref="canvasLayer" />
  </div>
  <div ref="textLayer" class="pdf__text-layer"></div>
  <div ref="annotationLayer" class="pdf__annotation-layer"></div>
</div>
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • We add another <div> for the Annotation Layer within pdfLayersWrapper.
  • The Annotation Layer is positioned on top of the Canvas Layer and Text Layer.

(2) Fetching and Loading the PDF

(Again, the same initial PDF loading steps as before.)

(3) Rendering the Annotation Layer

 async getAnnotations(pageProxy) {
  const annotations = await pageProxy.getAnnotations({ intent: "display" });
  return annotations;
 },
 async renderAnnotations(pdfPageProxy, annotationLayerContainer, viewport) {
   const annotations = await this.getAnnotations(pdfPageProxy);
   ...
   const annotationLayer = new PDFJS.AnnotationLayer({
    div: annotationLayerContainer,
    viewport: clonedViewport,
    page: pdfPageProxy
  });
  await annotationLayer.render({
    ...,
    annotations,
    linkService: new SimpleLinkService(),
    ...
  })
 }
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • getAnnotations retrieves all annotation info from the PDF page (links, form fields, etc.).
  • We clone the viewport to match the page’s dimensions for correct positioning.
  • renderAnnotations is then called to replace existing elements in annotationLayerContainer before rendering new ones. It does so by:
    • Creating a new AnnotationLayer instance to manage and render annotation elements.
    • Displaying the annotation layer by annotationLayer.render({...}), passing in the annotations and SimpleLinkService() to handle internal PDF links.

(4) Handling Internal Link Clicks

...
annotationLayerContainer.addEventListener("click", async (event) => {
  ...
  const annotationLinkId = annotations.find((ele) => ele.id === id);
  ...
  const pageIndex = await this.pdfDocProxy.getPageIndex(
    annotationLinkId.dest[0]
  );
  this.currentPage = pageIndex + 1;
});

Enter fullscreen mode Exit fullscreen mode

Explanation:

  • When a user clicks on a link inside the PDF, the event checks if it's an annotation.
  • If it is, we look up the destination page by getPageIndex() and see if it points to a different page in the PDF.
  • We then navigate to that page by updating this.currentPage.

(5) Handling Page Navigation

  • A simple navigation bar lets users switch between pages.
 <div class="page-navigation">
  <button :disabled="currentPage <= 1" @click="--currentPage">
   &larr; 
  </button>
  <span>{{ currentPage }}/{{ totalPages }}</span>
  <button :disabled="currentPage >= totalPages" @click="++currentPage">
   &rarr;
  </button>
 </div>
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • The currentPage value updates when navigation buttons are clicked.
  • watch: { currentPage(newValue) { ... } } ensures that all layers are re-rendered when the page changes.
  • This ensures all layers—Canvas, Text, and Annotation—stay in sync.

4. Structural Layer

Purpose

Think of the Structural Layer as the master layout manager. It keeps the Canvas, Text, and Annotation layers correctly aligned, scaled, and positioned when users zoom in or out, or when the window is resized.

This layer is a must-have when creating a PDF viewer as it serves as the foundation that glues all other layers together.

How It Works

  • The Structural Layer is typically implemented by a container element (<div>) that wraps all other layers.
  • Maintain consistent positioning among layers so they don’t drift apart during zooms or scrolls.
  • Act as the foundation that ties everything together, so your PDF viewer remains cohesive.

Use Cases

  • Ensure a consistent user experience across different screen sizes and resolutions.
  • Enable smooth zooming by scaling all layers proportionally.
  • Centralize navigation and layout logic (e.g. controlling how the viewer scrolls from one page to another.)

In our code examples, the <div ref="pdfLayersWrapper" class="pdf__layers"> element serves as the Structural Layer, wrapping for other layers:

<div ref="pdfLayersWrapper" class="pdf__layers">
  <div class="pdf__canvas-layer">
    <canvas ref="canvasLayer" />
  </div>
  <div ref="textLayer" class="pdf__text-layer"></div>
  <div ref="annotationLayer" class="pdf__annotation-layer"></div>
</div>
Enter fullscreen mode Exit fullscreen mode

Why is this important?

  • The Structural Layer wrapper ensures the Canvas, Text, and Annotation Layers all stay in sync, even when you zoom or flip through pages.
  • You can customize the wrapper to handle special behaviors like lazy-loading pages, scroll snapping and other performance optimizations.

How Layers Work Together

Here’s how all four layers in PDF.js work together in harmony:

  1. Rendering Process:
    Each page is first drawn onto the Canvas Layer (visual content). Next, the Text Layer is layered on top to enable selection and searching. Finally, the Annotation Layer is placed at the very top, providing interactive elements like links and form fields.

  2. Interactivity:
    The Annotation Layer handles user events (like clicking on links or typing into form fields), while the Text Layer ensures text can be highlighted, copied, or searched.

  3. PDF Viewer:
    The Structural Layer wraps everything up, ensuring that zooming, resizing, and navigation across pages remain consistent. It keeps all layers aligned and responsive, so users get a polished viewing experience.


Conclusion

I’ve used PDF.js in a variety of Vue.js projects, from simple PDF previews to fully interactive document systems, the layered architecture is always at the core.

Why does this matter for you?

  • You can optimize each layer for performance or user experience.
  • You can customize how your viewer handles interactions, text searches, or forms.
  • You’ll find it much easier to maintain or extend your PDF viewer code.

By understanding these layers, you will be able to optimize PDF.js implementations, customize features, and deliver a better user experience. Whether you’re building a simple Vue.js PDF viewer or a complex document management system, leveraging these layers effectively will set your project up for success.


Vue PDF Viewer: The PDF Viewer Built for Vue.js Developers 🚀

Vue PDF Viewer

If you enjoyed this article, I’d love for you to check out Vue PDF Viewer. It’s a PDF Viewer built from the ground up for Vue and Nuxt applications. Whether you’re just starting out or already running a large-scale app, Vue PDF Viewer got you covered.

Designed with developers in mind, Vue PDF Viewer offers:

  • Simple Vue integration to get you up and running fast.
  • Advanced customization so you can nail that perfect look and feel.
  • Responsive layouts that adapt to any device.
  • Developer-friendly APIs for more flexibility.

Your support motivates me to keep creating tools and content for the Vue community. Thank you for checking out Vue PDF Viewer. Happy coding! 🙏

Top comments (1)

Collapse
 
cmcnicholas profile image
Craig McNicholas

Really nice, this might save me some work coming up, I've been working with pdf.js recently, do you know of any way to read the "graphical" elements from the pdf. I've been trying to look for a solution that can give me the raw objects e.g. lines, images, vectors etc. but no cigar