Home >Web Front-end >JS Tutorial >Adventures in Aurelia: Creating a Custom PDF Viewer

Adventures in Aurelia: Creating a Custom PDF Viewer

尊渡假赌尊渡假赌尊渡假赌
尊渡假赌尊渡假赌尊渡假赌Original
2025-02-17 09:03:09740browse

Adventures in Aurelia: Creating a Custom PDF Viewer

This article was peer-reviewed by Vildan Softic. Thanks to all the peer reviewers at SitePoint for getting SitePoint content to its best!

Processing PDF files in web applications has always been very tricky. If you are lucky, your users just need to download the file. But sometimes, users need more features. I was lucky in the past, but this time, our users need the app to display PDF documents so that they can save metadata related to each page. Previously, people might have used expensive PDF plugins (such as Adobe Reader) to run in the browser to achieve this. However, after some time and experimentation, I found a better way to integrate a PDF viewer in my web application. Today, we will learn how to simplify PDF processing using Aurelia and PDF.js.

Core points

  • Use Aurelia and PDF.js to create a custom, efficient PDF viewer with features such as scaling and scrolling to enhance user interaction and performance.
  • Implement two-way data binding for attributes such as current page and zoom level in Aurelia, allowing seamless integration and dynamic updates in the application.
  • Develop PDF viewer into reusable Aurelia custom elements that allow you to add multiple viewers to your application without conflict.
  • Use PDF.js to process PDF rendering, support asynchronous operations and Web Worker to uninstall processing and improve UI response speed.
  • Address potential performance issues by considering virtual scrolling and other optimizations, especially in effectively handling large documents.
  • Explore the possibility of converting a custom PDF viewer to an Aurelia plugin to make it easy to integrate into other projects and applications.

Overview: Goal

Our goal today is to build a PDF viewer component in Aurelia that allows for two-way data flow between the viewer and our application. We have three main requirements:

  1. We want users to be able to load documents, scroll and zoom in/out with good performance.
  2. We want to be able to bidirectionally bind viewer properties (such as the current page and the current zoom level) to properties in the application.
  3. We want this viewer to be a reusable component; we want to be able to easily place multiple viewers in the application without conflict.

You can find the code for this tutorial in our GitHub repository, as well as a demo of the completed code here.

Introduction PDF.js

PDF.js is a JavaScript library written by the Mozilla Foundation. It loads PDF documents, parses files and related metadata, and renders the page output to the DOM node (usually <canvas></canvas> element). The default viewer included in the project provides support for embedded PDF viewers in Chrome and Firefox and can be used as standalone pages or resources (embedded in iframes).

This is really cool. The problem here is that the default viewer, while it has many features, is designed as a standalone webpage. This means that while it can be integrated into a web application, it basically has to run inside an iframe sandbox. The default viewer is designed to get configuration input through its query string, but we cannot easily change the configuration after initial loading, nor can we easily get information and events from the viewer. In order to integrate it with the Aurelia web application—including event handling and two-way binding—we need to create an Aurelia custom component.

Note: If you need a review about PDF.js, please check out our tutorial: Custom PDF rendering in JavaScript using Mozilla's PDF.js

Implementation

To achieve our goal, we will create an Aurelia custom element. However, we will not put the default viewer into our components. Instead, we will create our own viewer that connects to the PDF.js core and viewer library so that we can maximize control of our bindable properties and renderings. For our initial proof of concept, we will start with the Aurelia Skeleton application.

Photoscope code

As you can see from the link above, the skeleton application has many files, many of which we don't need. To simplify operations, we prepared a streamlined version of the skeleton and added some content to it:

  • A Gulp task for copying our PDF files to the dist folder (Aurelia is used for bundling).
  • PDF.js dependency has been added to package.json.
  • In the application root directory, index.html and index.css have undergone some initial style settings.
  • The empty copy of the file we are going to use has been added.
  • The file src/resources/elements/pdf-document.css contains some CSS styles for custom elements.

So let's get the application up and running.

First, make sure gulp and jspm are installed globally:

<code class="language-bash">npm install -g gulp jspm</code>

Then clone the skeleton and enter it:

<code class="language-bash">git clone git@github.com:sitepoint-editors/aurelia-pdfjs.git -b skeleton
cd aurelia-pdfjs</code>

Then install the necessary dependencies:

<code class="language-bash">npm install
jspm install -y</code>

Finally run gulp watch and navigate to http://localhost:9000. If everything goes according to plan, you should see a welcome message.

More settings

The next thing to do is find a few PDF files and put them in src/documents. Name them one.pdf and two.pdf. To maximize testing of our custom components, it is best that one of the PDF files is very long, such as War and Peace that can be found in the Gutenberg Project.

After putting the PDF file in place, open src/app.html and src/app.js (As agreed, the App component is the root component of the Aurelia application), and replace the code in it with these two files Code: src/app.html and src/app.js. In this tutorial, we will not discuss these files, but there are good comments in the code.

Gulp will automatically detect these changes and you should see our application UI rendering. That's the setting. Now start showing...

Create Aurelia custom element

We want to create a component that can be used directly for any Aurelia view. Since the Aurelia view is just an HTML snippet included in the HTML5 template tag, an example might look like this:

<code class="language-bash">npm install -g gulp jspm</code>

<pdf-document></pdf-document> tags are an example of custom elements. It and its properties (such as scale and page) are not native properties of HTML, but we can use Aurelia custom elements to create it. Custom elements are easy to create, using Aurelia's basic building blocks: views and ViewModel. Therefore, we will first build our ViewModel, named pdf-document.js, as shown below:

<code class="language-bash">git clone git@github.com:sitepoint-editors/aurelia-pdfjs.git -b skeleton
cd aurelia-pdfjs</code>

The main content to note here is the @bindable decorator; by creating binding properties with configuration defaultBindingMode: bindingMode.twoWay, and by creating handler methods (urlChanged, pageChanged, etc.) in our ViewModel, we can monitor and respond to changes to related attributes we place on custom elements. This will allow us to control our PDF viewer simply by changing the properties on the element.

We will then create the initial view paired with our ViewModel.

<code class="language-bash">npm install
jspm install -y</code>

(The following content is basically consistent with the original text, but some sentences have been adjusted in detail to maintain fluency and readability and avoid duplication.)

Integrated PDF.js

PDF.js is divided into three parts: core library (processing the parsing and interpretation of PDF documents), display library (building available APIs on top of the core layer), and web viewer plug-in (pre-built we mentioned earlier). Web page). For our purposes, we will use the core library through the display API; we will build our own viewer.

Show API exports a library object named PDFJS, which allows us to set some configuration variables and load our document using PDFJS.getDocument(url). The API is completely asynchronous - it sends and receives messages to the Web Worker, so it relies heavily on JavaScript Promise. We will mainly use the PDFDocumentProxy object asynchronously returned from the PDFJS.getDocument() method and the PDFPageProxy object asynchronously returned from the PDFDocumentProxy.getPage().

Although the documentation is a bit sparse, PDF.js has some examples of creating a basic viewer, here and here. We will build our custom components based on this.

Web Worker Integration

PDF.js uses Web Worker to uninstall its rendering tasks. Because of how Web Workers run in a browser environment (they are actually sandboxed), we are forced to load Web Workers using the direct file path to JavaScript files instead of the usual module loaders. Fortunately, Aurelia provides a loader abstraction, so we don't have to reference static file paths (this may change when we bundle the application).

If you are following our version of the repository, you may have installed the pdfjs-dist package, otherwise you will need to do this now (for example, using jspm jspm install npm:pdfjs-dist@^1.5.391). We will then inject Aurelia's loader abstraction using Aurelia's dependency injection module and use the loader to load the Web Worker file in our constructor as follows:

Loading page

PDF.js library handles the loading, parsing and display of PDF documents. It has built-in support for partial downloads and authentication. All we have to do is provide the URI of the relevant document. PDF.js will return a Promise object that resolves to a JavaScript object representing the PDF document and its metadata.

The loading and display of the PDF will be driven by our bindable attribute; in this case it will be the url attribute. Basically, when the URL changes, the custom element should ask PDF.js to issue a request to the file. We will do this in the urlChanged handler and make some changes to our constructor to initialize some properties and some changes to our detached method to clean up.

For each page of the document, we will create a <canvas></canvas> element in the DOM that is located in a scrollable container with a fixed height. To do this, we will use Aurelia's basic template functionality, using a repeater. Because each PDF page can have its own size and orientation, we will set the width and height of each canvas element according to the PDF page viewport.

Rendering page

Now that we have loaded the pages, we need to be able to render them to the DOM elements. To do this, we will rely on the rendering functionality of PDF.js. The PDF.js viewer library has an asynchronous API dedicated to rendering pages; their website has a great example showing how to create a renderContext object and pass it to the PDF.js render method. We extract this code from the example and wrap it in a render function:

Implement scrolling

To provide a familiar and seamless experience, our components should display the page as parts of a fully scrollable document. We can do this by having our container have a fixed height with scroll overflow, which can be achieved through CSS.

To maximize the performance of large documents, we will do the following things. First, we will use Aurelia's TaskQueue to batch change the DOM. Second, we will track the page that PDF.js has rendered so it doesn't have to redo the work it has done. Finally, we will render the visible page only after the scrolling stops by using Aurelia's debounce binding behavior. Here is how we will run when scrolling:

Achieve scaling

When we scale, we want to update the current zoom level. We will do this in the scaleChanged property handler. Basically, we will resize all the canvas elements to reflect the new viewport size for each page of a given scale. We will then re-render what is displayed in the current viewport and restart the loop.

The final result

Let's review our goals:

  1. We want users to be able to load documents, scroll and zoom in/out with good performance.
  2. We want to be able to bidirectionally bind viewer properties (such as the current page and the current zoom level) to properties in the application.
  3. We want this viewer to be a reusable component; we want to be able to easily place multiple viewers in the application without conflict.

The final code can be found in our GitHub repository, as well as a demo of the completed code here. Although there is still room for improvement, we have reached our goal!

(The following content is basically consistent with the original text, but some sentences have been adjusted in detail to maintain fluency and readability and avoid duplication.)

Post-project analysis and improvement

There is always room for improvement. It is always a good habit to conduct post-project analysis and determine areas that need to be solved in future iterations. Here are some things I want to upgrade in PDF viewer implementation:

Create plugin

Aurelia provides a plug-in system. Converting this proof of concept to an Aurelia plugin will make it an out-of-use resource for any Aurelia application. Aurelia Github repository provides a plugin skeleton project which will be a good starting point. This way, others can use this feature without rebuilding it!

Optimal

Processing PDF files in web applications has always been very tricky. But with the resources available today, we can achieve more than ever by combining libraries and their capabilities. Today, we have seen an example of a basic PDF viewer – a viewer that can be extended with custom features, because we have full control over it. The possibilities are endless! Are you ready to build something? Please let me know in the comments below.

FAQs (FAQs) about Aurelia Custom PDF Viewer Components

(The following content is basically consistent with the original text, but some sentences have been adjusted in detail to maintain fluency and readability and avoid duplication.)

The above is the detailed content of Adventures in Aurelia: Creating a Custom PDF Viewer. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn