Open and annotate PDFs from your Vue.js app

Table of contents

    Open and annotate PDFs from your Vue.js app
    Summary

    Learn how to integrate our JavaScript PDF library with Vue.js to create a powerful PDF viewer and annotation tool for your web application. This comprehensive tutorial demonstrates setting up Vue.js CLI, installing Nutrient Web SDK, and building a reusable Vue component that supports opening, viewing, and annotating PDF documents. Discover how to leverage WebAssembly technology for optimal performance while maintaining compatibility across all modern browsers.

    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 Nutrient Web SDK, a JavaScript PDF library that can be used with (and without) any JavaScript framework, including Vue.js(opens in a new tab). 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.

    Nutrient Web SDK 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 Nutrient repository(opens in a new tab). You’ll build a small app in a single HTML file that will fetch all the assets needed to load and run Nutrient Web SDK 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
    Terminal window
    npm install -g @vue/cli
    1. Create a new Vue.js project
    Terminal window
    vue create my-app

    Select Vue 3, depending on your preference.

    1. Navigate to the project directory
    Terminal window
    cd my-app

    2. Installing Nutrient Web SDK

    1. Add the Nutrient dependency
    Terminal window
    yarn add pspdfkit
    # or
    npm install pspdfkit
    1. Prepare the Nutrient library
    • Create a directory under public:
    Terminal window
    mkdir -p public/js
    • Copy the Nutrient library:
    Terminal window
    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 Nutrient.

    3. Creating the Nutrient 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 Nutrient 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 Nutrient 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 Nutrient. This ensures the viewer instance is properly cleaned up:

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

    To make sure Nutrient 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 Nutrient, 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 Nutrient:

    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 Nutrient, 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:

    Terminal window
    npm run serve

    When you load a PDF, the Nutrient 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 Nutrient 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 Nutrient instance is ready.

    1. Handle the loaded event

    In the script, add a method to handle the loaded event, which provides the Nutrient 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 Nutrient 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 Nutrient.

    Step 1 — Set up the component data

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

    export default {
    data() {
    return {
    instance: null, // This will hold the Nutrient 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 Nutrient supports various annotation types, like text and images. Find out more about how to use PSPDFKit.Instance#create() by browsing the Nutrient Web SDK API reference.

    Conclusion

    In this blog, you learned how to integrate Nutrient’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 Nutrient, 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 Nutrient Web SDK?

    Nutrient Web SDK 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 Nutrient Web SDK without a backend?

    Yes. You can use Nutrient Web SDK as a standalone library without a backend, making it flexible for various application setups.

    What file formats does Nutrient support?

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

    Is there a trial version of Nutrient available?

    Yes. Nutrient 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 Nutrient support page.

    Miguel Calderón

    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.

    Explore related topics

    FREE TRIAL Ready to get started?