Blog Post

Open and Annotate PDFs from Your Vue.js App

Illustration: Open and Annotate PDFs from Your Vue.js App
Information

This article was first published in October 2018 and was updated in August 2024.

PDF documents are the preferred format for many things, including sharing information with formatting and enhanced data visualization. Therefore, it’s not surprising that the ability to open and annotate PDF documents has become an increasingly demanded feature for any web application as soon as it grows in size and complexity.

However, adding such a feature to a web application usually means incrementing the number of “moving parts” of a codebase by orders of magnitude: PDF is a complex file format, which may deem the task overwhelming for any development team.

You can simplify this task significantly by making use of PSPDFKit for Web, a JavaScript PDF library that can be used with (and without) any JavaScript framework, including Vue.js. It supports all modern mobile and desktop browsers (Chrome, Firefox, Safari, and Edge) and multiple languages, and it makes good use of the latest technologies available — like WebAssembly — to make the experience as performant as possible.

PSPDFKit for Web comes in two flavors: server-backed and standalone. This means you can set it up as a shared collaboration tool that’s integrated with your server backend, or as a client-side library with all the features you may need for your PDF document handling.

To allow developers to easily embed our PDF library in their applications, there are several integration examples available. In this article, you’ll learn how to integrate our Vue.js PDF library, which you can clone from the public PSPDFKit repository. You’ll build a small app in a single HTML file that will fetch all the assets needed to load and run PSPDFKit for Web in a Vue.js app.

The final result will look like the image below in your browser. It’ll consist of a simple UI that allows you to open, view, and annotate PDF documents from within your Vue.js app.

Integration with Vue.js example

Opening a PDF in Vue.js

To get the example running, you need the following tools:

1. Setting Up Vue.js CLI

  1. Install the Vue.js CLI

npm install -g @vue/cli
  1. Create a new Vue.js project

vue create my-app

Select Vue 3, depending on your preference.

  1. Navigate to the project directory

cd my-app

2. Installing PSPDFKit for Web

  1. Add the PSPDFKit dependency

yarn add pspdfkit
# or
npm install pspdfkit
  1. Prepare the PSPDFKit library

  • Create a directory under public:

mkdir -p public/js
  • Copy the PSPDFKit library:

cp -R ./node_modules/pspdfkit/dist/pspdfkit-lib public/js/pspdfkit-lib
  1. Configure the MIME types

Ensure your server has the Content-Type: application/wasm MIME type set. This is required for PSPDFKit.

3. Creating the PSPDFKit Vue Component

  1. Create the component file

  • In the src/components directory, create a file named PSPDFKitContainer.vue.

  1. Set up the props

Start by defining the pdfFile prop in your component. This prop is required and will hold the path to the PDF file you want to display. By passing this as a prop, you can dynamically load different PDF files into the viewer from outside the component:

export default {
	props: {
		pdfFile: {
			type: String,
			required: true,
		},
	},
};
  1. Create the loadPSPDFKit method

Next, define the loadPSPDFKit method. This method is responsible for loading the PSPDFKit viewer with the PDF file provided through the pdfFile prop. Since PSPDFKit.load() returns a Promise, define the method as async.

Make sure to unload any existing PSPDFKit instance before loading a new one. This prevents memory leaks and ensures the correct PDF is always displayed:

methods: {
  async loadPSPDFKit() {
    PSPDFKit.unload('.pdf-container');
    return PSPDFKit.load({
      document: this.pdfFile,
      container: '.pdf-container',
    });
  },
},
  1. Clean up on component destruction

To avoid lingering references when the component is destroyed, use the beforeDestroy lifecycle hook to unload PSPDFKit. This ensures the viewer instance is properly cleaned up:

beforeDestroy() {
  PSPDFKit.unload('.pdf-container');
},
  1. Load PSPDFKit when the component is mounted

To make sure PSPDFKit is loaded as soon as the component is mounted, call the loadPSPDFKit method within the mounted lifecycle hook. Once the method resolves with the instance of PSPDFKit, emit a loaded event with the instance as the payload. This event enables you to use the instance elsewhere in your application:

mounted() {
  this.loadPSPDFKit().then((instance) => {
    this.$emit('loaded', instance);
  });
},
  1. Watch for changes to the pdfFile prop

To handle dynamic changes to the pdfFile prop, use a watch handler. This allows the component to reload the PDF whenever the prop changes. Additionally, include a check to ensure the new value is valid before attempting to reload PSPDFKit:

watch: {
  pdfFile(val) {
    if (val) this.loadPSPDFKit();
  },
},

Full Code Overview

Here’s the final code that combines all these steps:

<template>
  <div class="pdf-container"></div>
</template>

<script>
import PSPDFKit from 'pspdfkit';

export default {
  props: {
    pdfFile: {
      type: String,
      required: true,
    },
  },
  methods: {
    async loadPSPDFKit() {
      PSPDFKit.unload('.pdf-container');
      return PSPDFKit.load({
        document: this.pdfFile,
        container: '.pdf-container',
      });
    },
  },
  beforeUnmount() {
    PSPDFKit.unload('.pdf-container');
  },
  mounted() {
    this.loadPSPDFKit().then((instance) => {
      this.$emit('loaded', instance);
    });
  },
  watch: {
    pdfFile(val) {
      if (val) this.loadPSPDFKit();
    },
  },
};
</script>

<style scoped>
  .pdf-container {
    height: 100vh;
  }
</style>

This component is now fully capable of loading PSPDFKit, rendering PDFs, unloading when destroyed, and dynamically switching PDFs based on the provided pdfFile prop.

Use the scoped flag to make sure this CSS rule only applies to DOM nodes within the component itself. You can still overwrite CSS rules from outside, but it’s good practice to prevent accidental overrides across your project. This way, the component can also be freely used between various projects without having to reapply styles in each of them.

Importing and Using the PSPDFKitContainer.vue Component

Now that you’ve created the PSPDFKitContainer.vue component, the next step is to import and use it within the main Vue.js page, where you want to display the PDF.

  1. Set up the template

In your main Vue component (e.g. App.vue), add the template structure that includes a file input for selecting the PDF file and the PSPDFKitContainer component to display the selected file:

<template>
	<div id="app">
		<input type="file" @change="openDocument" />
		<PSPDFKitContainer :pdfFile="pdfFile" />
	</div>
</template>
  • The <input> element is used to select the PDF file.

  • The PSPDFKitContainer component displays the PDF, with pdfFile as a prop containing the file path.

  1. Import the PSPDFKitContainer component

In the script section, import the PSPDFKitContainer component so that it can be used in the template:

<script>
	import PSPDFKitContainer from '@/components/PSPDFKitContainer';

	export default {
		data() {
			return {
				pdfFile: this.pdfFile || '/example.pdf', // Default PDF file to display.
			};
		},
		components: {
			PSPDFKitContainer,
		},
		methods: {
			openDocument() {
				if (this.pdfFile) {
					window.URL.revokeObjectURL(this.pdfFile); // Clean up old file reference.
				}
				// Update `pdfFile` with the new file selected by the user.
				this.pdfFile = window.URL.createObjectURL(event.target.files[0]);
			},
		},
	};
</script>
  • The pdfFile data property stores the path to the currently selected PDF.

  • The openDocument method handles the file input change, generating a URL for the selected file and assigning it to pdfFile.

  1. Prepare your PDF document

Add the example.pdf file to the public directory of your Vue.js project. The public directory serves static files directly at the root of your project, making it easy to access resources like images, styles, and PDF files.

  1. Run the application

With everything set up, run your Vue application:

npm run serve

When you load a PDF, the PSPDFKit viewer will display the document, allowing you to read, annotate, and print it directly from your browser.

Dynamic Loading and Event Handling

While the setup above works well for displaying a PDF, it’s also essential to manage the PSPDFKit instance when switching between different PDF files. To handle this, watch for changes in the pdfFile prop and reload the viewer when a new file is selected. You also need to handle the instance returned by PSPDFKit.load() for advanced functionality like annotations.

  1. Update the template to handle loaded events

Modify the template to listen for the loaded event emitted by the PSPDFKitContainer component:

<template>
  <div id="app">
    <input type="file" @change="openDocument" />
    <PSPDFKitContainer :pdfFile="pdfFile" @loaded="handleLoaded" />
  </div>
</template>

The @loaded="handleLoaded" directive listens for when the PSPDFKit instance is ready.

  1. Handle the loaded event

In the script, add a method to handle the loaded event, which provides the PSPDFKit instance for further interaction:

<script>
import PSPDFKitContainer from '@/components/PSPDFKitContainer';

export default {
  data() {
    return {
      pdfFile:  this.pdfFile || '/example.pdf',
    };
  },
  components: {
    PSPDFKitContainer,
  },
  methods: {
    openDocument(event) {
      if (this.pdfFile) {
        window.URL.revokeObjectURL(this.pdfFile);
      }
      this.pdfFile = window.URL.createObjectURL(event.target.files[0]);
    },
    handleLoaded(instance) {
      console.log("PSPDFKit has loaded: ", instance);
      // Perform any operations with the PSPDFKit instance, like adding annotations.
    },
  },
};
</script>

Annotating the PDF in Vue.js

Here’s a step-by-step guide to adding custom annotations to your PDF in a Vue.js app using PSPDFKit.

Step 1 — Set Up the Component Data

First, add a data() function to your component to store the PSPDFKit instance:

export default {
	data() {
		return {
			instance: null, // This will hold the PSPDFKit instance
		};
	},
	// ...
};

Step 2 — Update the loadPSPDFKit Method

Customize the toolbar to include a custom button that adds an ink annotation. Modify the loadPSPDFKit method to include the custom toolbar button, and set up the onPress handler:

methods: {
  async loadPSPDFKit() {
	try {
	PSPDFKit.unload('.pdf-container');
	const instance = await PSPDFKit.load({
		document: this.pdfFile,
		container: '.pdf-container',
		toolbarItems: [
		{
			type: 'custom',
			title: 'Add Ink Annotation',
			className: 'addInkAnnotation',
			name: 'addInkAnnotation',
			onPress: async () => {
			if (!instance) {
				console.error('PSPDFKit instance is not available');
				return;
			}

			const inkAnnotation = new PSPDFKit.Annotations.InkAnnotation({
				pageIndex: 0,
				lines: PSPDFKit.Immutable.List([
				PSPDFKit.Immutable.List([
					new PSPDFKit.Geometry.DrawingPoint({ x: 0, y: 0 }),
					new PSPDFKit.Geometry.DrawingPoint({ x: 100, y: 100 }),
				]),
				]),
			});

			try {
				const createdAnnotations = await instance.create(inkAnnotation);
				console.log('Created Ink Annotations:', createdAnnotations);
			} catch (error) {
				console.error('Error creating ink annotation:', error);
			}
			},
		},
		],
	});
	this.instance = instance;
	this.$emit('loaded', instance);
	} catch (error) {
	console.error('Error loading PSPDFKit:', error);
	}
  },
}

Step 3 — Handle the instance in the Component Lifecycle

Update the mounted lifecycle hook and the watch property to ensure the instance is updated when needed:

mounted() {
  this.loadPSPDFKit().then((instance) => {
    this.instance = instance; // Store the PSPDFKit instance.
    this.$emit('loaded', instance); // Emit an event when loaded.
  });
},
watch: {
  pdfFile(val) {
    if (val) {
      this.loadPSPDFKit().then((instance) => {
        this.instance = instance; // Update the instance when the PDF changes.
      });
    }
  },
},

Rebuild your Vue.js app and load a PDF. The toolbar will now include a button labeled Add Ink Annotation. When clicked, it adds an ink annotation with predefined drawing points to the first page of your PDF. Adjust the drawing points and other parameters as necessary to fit your specific requirements.

This post focuses on adding an ink annotation, but PSPDFKit supports various annotation types, like text and images. Find out more about how to use PSPDFKit.Instance#create() by browsing the PSPDFKit for Web API reference.

Conclusion

In this blog, you learned how to integrate PSPDFKit’s JavaScript PDF library with the Vue.js framework. Once you’ve got it up and running, you can enable additional features and customizations in your application:

At PSPDFKit, we offer a commercial, feature-rich, and completely customizable Vue.js PDF library that’s easy to integrate and comes with well-documented APIs to handle advanced use cases. Try it for free, or visit our web demo to see it in action.

FAQ

What is PSPDFKit for Web?

PSPDFKit for Web is a JavaScript library that allows developers to integrate powerful PDF viewing and annotation capabilities into web applications, including those built with Vue.js.

Can I use PSPDFKit for Web without a backend?

Yes, you can use PSPDFKit for Web as a standalone library without a backend, making it flexible for various application setups.

What file formats does PSPDFKit support?

PSPDFKit primarily supports PDF files, providing extensive features for viewing, annotating, and manipulating PDF documents.

Is there a trial version of PSPDFKit available?

Yes, PSPDFKit offers a free trial that allows you to explore its features before committing to a purchase.

How can I report issues or get support?

You can report issues or request support through the PSPDFKit support page on their website.

Author
Miguel Calderón Web Team Lead

As part of the Web Team, Miguel loves finding new challenges and trying to determine what the newest technologies can do to improve our product. Writing fiction, reading, and traveling are his passions the rest of the time.

Share post
Free trial Ready to get started?
Free trial