Using the Camera API in a PWA with Capacitor

Using the Camera API in a PWA with Capacitor

Follow Josh Morony on

One of the key ideas behind the Capacitor project which was created by the Ionic team, is to provide access to browser/native features through a single API no matter what platform the application is running on. This philosophy makes the one codebase/multiple platforms approach to building applications much more feasible.

Geolocation is a good example of where this can simplify things a great deal for us. How we implement Geolocation may depend on what platform we are running on, e.g: iOS, Android, or the web. However, we don’t need to write a bunch of conditions into our application and run different code based on the platform (or perhaps build slightly different versions of our application for each platform), we can just make a single call to Capacitor’s Geolocation API:

Geolocation.getCurrentPosition();

It isn’t always so simple. Naturally, the web and native applications have accesss to a different set of functionality, and not all native functionality is going to be able to run on the web. If a particular Capacitor API does not have a web implementation, then you won’t be able to use it.

If you try to use the Camera API, for example, you are going to find that it won’t work:

Uncaught (in promise) TypeError: cameraModal.componentOnReady is not a function
    at CameraPluginWeb.<anonymous> (app-home.entry.js?s-hmr=225807175348:648)

There is good news though, we can actually get the Camera API to work on the web as well - as long as the user has some kind of web camera we will be able to take a photo using that camera.

The Ionic team have another library called PWA Elements. This library provides web-based implementations for some of Capacitor’s APIs. Where it is possible for the functionality to be implemented through the web, the PWA Elements library can provide the interface elements required to provide a sleek experience on the web as well. The Camera API is one example of this. If we install the @ionic/pwa-elements package, we can launch a camera modal that will allow the user to take a photo on the web.

Camera API on the web with Capacitor

In this tutorial, we will be walking through exactly how to do this. We will be using an Ionic/StencilJS application as an example, but the same general concept will apply no matter what kind of framework/application you have Capacitor set up in.

Before We Get Started

This tutorial will assume that you already have Capacitor installed in your project, and that you are already comfortable with creating applications in the framework of your choice.

If you are not familiar with Capacitor you can find more information about getting started in the documentation.

1. Installing PWA Elements

We will first need to install the PWA Elements package. To do that, just run the following command in your project:

npm install @ionic/pwa-elements

You will also need to set up pwa-elements in your code, but this will vary depending on what framework/approach you are using. I am using Ionic/StencilJS for this example, and all you will need to do to set up PWA Elements in this environment is to add the following import to the src/global/app.ts file:

import "@ionic/pwa-elements";

If you are using Angular or React or some other framework (or none at all), the steps for loading PWA Elements can be found here: Installation Instructions for PWA Elements.

2. Trigger the Camera API

Next, we will need to implement the code for capturing a photo. The Capacitor Camera API makes this simple enough, we will just need to implement a function like the following:

import { Component, h } from "@stencil/core";
import { Plugins, CameraResultType } from "@capacitor/core";

const { Camera } = Plugins;

@Component({
  tag: "app-home",
  styleUrl: "app-home.css"
})
export class AppHome {
  async takePicture() {
    const image = await Camera.getPhoto({
      resultType: CameraResultType.Uri
    });
    console.log(image);
  }

  render() {
    return [
      <ion-header>
        <ion-toolbar color="primary">
          <ion-title>Home</ion-title>
        </ion-toolbar>
      </ion-header>,

      <ion-content class="ion-padding">
        <ion-button onClick={() => this.takePicture()}>Take photo</ion-button>
      </ion-content>
    ];
  }
}

Again, this will look a little different depending on the framework you are using, but the same basic concept is the same. Once you serve your application in the browser and trigger that takePicture method the Ionic/Capacitor Camera API will ask for permission to use the camera. Once you grant permission it will display a camera preview overlay that looks like this:

Camera API on the web with Capacitor

This will stream video into the application from the computers web camera, and you will be able to see this live feed as you move around. When you click the circle button a photo will be captured and the photo will be resolved as the result of the getPhoto call to the Camera API based on the resultType that is being used.

In the example above, we are using Uri which will store the resulting photo as a BLOB locally. If you were to go to this address in your browser, for example (using your own result from the getPhoto method and in an Ionic/StencilJS application):

blob:http://localhost:3333/7888d45e-4e65-4d19-a157-e06dc5d147ff

You would be able to view the photo. You could also instead set the resultType to Base64 or DataUrl to return the photo data in those formats instead, e.g:

{dataUrl: "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD…8Nj80tM5paCR1FNzRmgBwpaaKWmAuaUGm0oNMQpNFJRTA/9k=", format: "jpeg"}

If you are attempting to create a solution that works on both the web and native iOS/Android, you may need to take a different approach to what you actually do with the resulting photo based on the platform. For example, on iOS/Android you might want to implement a solution like the one discussed in this tutorial: Using the Capacitor Filesystem API to Store Photos. That tutorial describes a process for permanently storing photos natively, but you can’t use that same approach for the web version, so you would need to implement slightly different solutions depending on what it is exactly that you want to do - you might want to try some sort of local storage solution, or perhaps it is more appropriate for you to store the images on some kind of server.

Summary

The PWA Elements package provides a really sleek way to implement the Camera API in a web environment, whilst also making it possible to utilise the standard native camera functionality when the application is running in that environment. This goes a long way to helping us get to that dream of writing code that just works on all platforms, but for now at least, there are still going to at least be some differences that need to be catered for which might result in a few extra if/else conditions if we want to run on all platforms.

Check out my latest videos: