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.
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"})exportclassAppHome{asynctakePicture(){const image =await Camera.getPhoto({
resultType: CameraResultType.Uri
});
console.log(image);}render(){return[<ion-header><ion-toolbarcolor="primary"><ion-title>Home</ion-title></ion-toolbar></ion-header>,<ion-contentclass="ion-padding"><ion-buttononClick={()=>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:
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.