Using Web APIs to Create a Camera Application
Tags: webapis, canvas, mediacaptureandstreams, camera
Published: 2023-10-25 04:04:14

---
title: Using Web APIs to Create a Camera Application
published: true
description: Use Web APIs to create a camera application.
tags: WebAPIs, Canvas, MediaCaptureandStreams, Camera
canonical_url: https://code-literacy.medium.com/using-web-apis-to-create-a-camera-application-26fc0fc2b8c3/
---
![Camera](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/khtc522k3oknzgiknr6r.jpg)

[code] (https://github.com/rebeccapeltz/webapi-camera)
[demo] (https://www.beckypeltz.me/webapi-camera/)

I enjoy working on applications that connect the physical and digital worlds. I'll show you how to build a camera application in this post.

## Web APIs

If you were working as a web developer in 2010, you might remember [the blog post in A List Apart](https://alistapart.com/article/responsive-web-design/) that introduced the idea of Responsive layouts to a community that was wondering how to work with so many devices and screen sizes. Media queries, fluid layouts, and flexible images allowed developers to create "m-dot" websites like `https://m.mydomain.com` which were served to mobile browsers. CSS has continued to evolve to address the needs of mobile web development.

Native frameworks and languages that could take advantage of the technology available only on mobile devices like cameras and GPS sensors and the ability to provide access without network service led to applications with features not available in web applications. The mobile user could use cameras to take pictures, GPS to navigate the roads, and access the application even when no network was available.

Over that last decade, the W3C's Web Applications Working Group has created specs that, when implemented in a browser, enable web developers to create many of the features found on mobile devices. In this blog, I will describe how to create a web application that makes it easy for a user to take a picture on a desktop or mobile device and then download or share it using Web APIs.

Web APIs vary with the implementation per browser. It is wise to check [caniuse.com](https://caniuse.com/) to verify that the browsers we expect our application to run in are supported. Often, additional research or experimentation is needed to see differences in implementation.

## A Camera Application for Desktop and Mobile

The images below demonstrate the operation of the camera. Buttons are enabled based on the state of the camera. Access the camera demo [here](https://www.beckypeltz.me/webapi-camera/), and try it out before digging into the code.

![Press start to open video camera](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7muabc14mt4jaj07nucy.jpg) 

--- 

![Press snapshot to take a picture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sb2ibhjtlq16lj3ssb0z.jpeg) 

---  

![Press Flip Location to Swap the Location of the Video and Canvas Containers
](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mljq41f8l0ylvo26q8ry.jpeg) 

---  

![Flip Camera Location is Helpful on a Mobile Device
](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/az7fye7b7iepwefkif3z.png) 

---  

![Back and Front-Facing Devices on a Mobile Phone
](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2n4t86j1rzjenpue2sn8.jpeg)  

---

Press Start to Open the Video CameraPress Snapshot to Take a PicturePress Flip Location to Swap the Location of the Video and Canvas ContainersFlip Camera Location is Helpful on a Mobile DeviceBack and Front-Facing Devices on a Mobile Phone

Let's see how to create this web application using Web APIs.

## Web APIs

Documentation of [Web APIs and interfaces](https://developer.mozilla.org/en-US/docs/Web/API) is located on the MDN (Mozilla Developer Network) website. The term "Web API" can refer to browser or server APIs. In this article, it refers to **Browser APIs**.

The Web API Camera Application is built using these Web APIs:

*   DOM
*   HTML DOM
*   Media Capture and Streams
*   Canvas
*   Web Share
*   Permissions

We'll also see how browser permissions require a response from the user because they must allow the application to access hardware.

DOM and DOM HTML
================

The DOM (Document Object Model) is the first Web API. The interfaces in the DOM are accessible using JavaScript. Styling is provided by Cascading Stylesheets (CSS).

The DOM is a data structure the browser constructs when it parses HTML. This structure represents a Document. The Document object is loaded into the Window object. The Document and Window provide the interfaces for querying and maintaining the application in the browser.

The camera application uses JavaScript to interact with the DOM. This interaction includes adding and removing elements, adding and removing styles, and firing events when the user interacts with the Document.

The application comprises a set of buttons turned on and off according to what the user can do at any given time. The image created from the video stream is drawn onto a canvas HTML element.

The application consists of five buttons enabled based on the user's interaction with the Document. The **Start** button reveals a container loaded with a video stream, representing the camera view. The first time the user opens the video stream, there will be a prompt for permission because of a request to access media devices. A select element is loaded and displayed. A single option will open a back-facing camera if the application is run on a desktop computer. On a mobile device, there will also be an option to use a front-facing camera. Some mobile devices have multiple back and front cameras, and all these options will be available.

When the user clicks the **Snapshot** button, a container with a canvas element is revealed, and the current video frame is loaded into the canvas element. This also enables the **Download** button and displays a **Share** button. If the browser is allowed to **Share**, clicking this button will open up sharing options using different network applications like email and messaging. The **Download** button downloads the image to the local hard drive. Finally, a **Stop** button turns off all the other buttons and hides the containers while enabling the **Start** button.

## DOM Click Event Listeners

The code below shows how to initialize the buttons when the Document is loaded. The **DOMContentLoaded** event is fired after the HTML tags have been parsed and the elements rendered. We use the `querySelector` interface to get references to DOM HTML elements identified by their id or class name.

```
document.addEventListener("DOMContentLoaded", (e) => {  
 ...  
  
  // set video  
  data.videoEl = document.querySelector("#video");  
  data.canvasEl = document.querySelector("#canvas");  
  
  // attach click event listeners  
  document.querySelector("#camera").addEventListener("click", (e) => {  
    start();  
  });  
  document.querySelector("#snapshot").addEventListener("click", (e) => {  
    snapShot();  
  });  
  document.querySelector("#stop").addEventListener("click", (e) => {  
    stop();  
  });  
  document.querySelector("#download").addEventListener("click", (e) => {  
    download();  
  });  
  document.querySelector("#share").addEventListener("click", (e) => {  
    share();  
  });  
  document.querySelector("#flip").addEventListener("click", (e) => {  
    flip();  
  });  
  
 ...  
});
```

## DOM Adding and Removing Elements

The video device selector that allows users to select front or back-facing cameras on mobile devices is dynamically loaded based on the available devices. Once we have found available devices, we can add them to the DOM using `document.createElement` and `appendChild` to load the Select element options.

```
 let option = {};  
  option.text = device.label;  
  option.value = device.deviceId;  
  data.options.push(option);  
  var selection = document.createElement("option");  
  selection.value = option.value;  
  selection.text = option.text;  
  document.querySelector("#device-option").appendChild(selection);
```

The **Download** functionality is implemented using hidden anchor tags, which are added and removed as needed.

```
// add a link  
 let a = document.createElement("a");  
 document.querySelector("body").append(a);  
  
// remove  
 document.querySelector("body").remove(hiddenLink);  
  

```

## DOM Adding and Removing Styles

As the user moves through the steps needed to access the camera, take a Snapshot, and then download or share, elements are displayed and hidden using two different classes.

The `hidden` class is `display:none` which means that when this class is added to an element, it is removed from the DOM. This is used to hide and show the video and canvas containers. The canvas container holds the **Snapshot**.

```
function hide(id) {  
  let el = document.querySelector(\`#${id}\`);  
  if (!el.classList.contains("hidden")) {  
    el.classList.add("hidden");  
  }  
}  
function show(id) {  
  let el = document.querySelector(\`#${id}\`);  
  if (el.classList.contains("hidden")) {  
    el.classList.remove("hidden");  
  }  
}
```

The `hidden-link` class uses `visibility:hidden` which leaves the element in the DOM, but it is not visible. This hidden anchor tag will enable the download of the **Snapshot**.

```
 a.classList.add("hidden-link")
```

The **Flip** button, made visible when the user clicks on **Snapshot,** causes the video and canvas containers to change location. This is especially helpful on a mobile screen if you want to look at the image after you've created a **Snapshot**. The canvas container is initially rendered to the right or below the video container on a smaller viewport.

The video and canvas containers are rendered using the `display: flex` command. We add toggle logic to flip the containers by adding and removing the CSS command.

```
if (videoCanvasContainer.classList.contains("flex-row")){  
    videoCanvasContainer.classList.remove("flex-row");  
    videoCanvasContainer.classList.remove("flex-wrap");  
    videoCanvasContainer.classList.add("flex-row-reverse");  
    videoCanvasContainer.classList.add("flex-wrap-reverse");  
  } else{  
    videoCanvasContainer.classList.remove("flex-row-reverse");  
    videoCanvasContainer.classList.remove("flex-wrap-reverse");  
    videoCanvasContainer.classList.add("flex-row");  
    videoCanvasContainer.classList.add("flex-wrap");  
  }
```

## Managing State for the Application

The JavaScript for this application can be found in a single file: _script.js_. The application is both data and user-driven. User actions result in system changes maintained in a JavaScript object.

```
const data = {  
  videoEl: null,  
  canvasEl: null,  
  fileData: null,  
  currentStream: null,  
  constraints: {},  
  selectedDevice: null,  
  options: \[\],  
};
```
## Navigator

The Navigator interface is accessible from the Windows object. The Navigator gets information about the user request in the User Agent (UA) header. The UA represents the person making the request. The UA header is a string that provides information like this :

```
Mozilla/5.0 (Macintosh; Intel Mac OS X 10\_15\_7)   
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36
```

We'll use the Navigator to access the user's media stream. We can discover our users' video and audio devices from the media stream. For this camera application, we'll be interested in the video devices.

### Permissions

The **Media Capture and Streams API,** which opens up the video stream in the video container, can query the [**Permissions API**](https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API#permission-aware_apis) to determine if the user allows the application access to the video.

The rule for prompting for permission when the `navigator.getUserMedia` function is called varies among browsers. The image below shows the user permission request on Safari. The choice to allow access to the camera will remain in effect on Safari until there is a page refresh. On Chrome, the permission remains in effect between page loads.

### Media Capture and Streams

The **Media Capture and Streams API** is part of the **Web Real Time Communication API (WebRTC).** WebRTC  is used for user video and audio communication. We'll use just the video capture to provide a stream of video frames from which we can create a **Snapshot**.

In the **Start** function, we call a `getDevices()` function. This function aims to discover the devices available and load them in the **Video Device** Select element.

```
async function getDevices() {  
  // does the browser support media devices and enumerateDevices?  
  if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {  
    console.log("enumerated devices not supported");  
    return false;  
  }  
    
  // call getUserMedia to trigger check and prompt for permissions  
  await navigator.mediaDevices.getUserMedia({ video: true });  
    
  // get the list of video devices  
  let allDevices = await navigator.mediaDevices.enumerateDevices();  
...
```

### Initial Selected Device

Once the devices are loaded, we set them `selectedDevice` to the first, and sometimes only, device in the list. Recall that the desktop video is not back-facing.

Next, we place constraints for the video stream we will render. The `setConstraints` function will be called when the user first clicks **Start** and later when they select different devices if multiple devices are available.

```
function setConstraints() {  
  const videoConstraints = {};  
  videoConstraints.deviceId = {  
    exact: data.selectedDevice,  
  };  
  
  data.constraints = {  
    video: videoConstraints,  
    audio: false,  
  };  
}
```

Once the constraints are set, the `getMedia` function is called to start streaming media from the device's camera. The stream `window.stream` is bound to the video element.

```
async function getMedia() {  
  try {  
    data.stream = await navigator.mediaDevices.getUserMedia(data.constraints);  
    window.stream = data.stream;  
    data.currentStream = window.stream;  
    data.videoEl.srcObject = window.stream;  
    return true;  
  } catch (err) {  
    throw err;  
  }  
}
```

With the video streaming, the **Start** button is disabled, and the **Snapshot** and **Stop** buttons are enabled.

```
if (resultMedia) {  
    disableBtn("camera");  
    enableBtn("stop");  
    enableBtn("snapshot");  
  }
```

### Device Change

If the user has the option to change the selected device, the `deviceOptionChange` function calls `deviceChange` to stop the camera, close the canvas container, and then call `getMedia` to reopen the camera using the newly established device.

```
async function deviceChange() {  
  stopVideoAndCanvas();  
  setConstraints();  
  const result = await getMedia();  
  console.log("device change:", result);  
}
```

### Calling GetMedia Twice

You may have noticed that the GetMedia function is called twice when the user presses Start. To access devices from the Navigator, the user must get permission. The function call to getUserMedia triggers the permission prompt**:**

```
 await navigator.mediaDevices.getUserMedia({ video: true });
```

We want to get the list of devices before loading the video, so the first call to `getUserMedia` takes care of access to the devices. We use the selected device to set the constraints that provide the video stream. The second call opens the video stream and assigns it to our data state.

```
data.stream = await navigator.mediaDevices.getUserMedia(data.constraints);
```

## Snapshot

We'll use the **Canvas API** and element to create a snapshot. The Snapshot is made from the video stream frame visible when the **Snapshot** button is pressed.

When the user clicks **Snapshot,** we show the canvas container and the **Flip** button. Clicking on the **Flip** button when using a mobile device makes it easier to see what was captured from the video stream.

The canvas HTML element is assigned a width and height equal to the dimensions of the video element. We then use the **Canvas API** to draw a 2-dimensional image from the video element onto the canvas element. The signature we use passes the **video** element (image), **dx** (horizontal distance of the image from the left side of the canvas container), **dy** (vertical) distance of the image from the top of the canvas container, the **width** of the container, and the **height** of the container.

`drawImage(image, dx, dy, dWidth, dHeight)`

Finally, we use the `toDataURL` to capture the data that makes up the frame from the video. Looking at the string created, we can see the type and then base64 values that represent the image.

```
data:image/jpeg;base64,/9j/4AAQSk...
``````
function snapShot() {  
  show("canvas-container");  
  show("flip-button");  
  data.canvasEl.width = data.videoEl.videoWidth;  
  data.canvasEl.height = data.videoEl.videoHeight;  
  data.canvasEl  
    .getContext("2d")  
    .drawImage(data.videoEl, 0, 0, data.canvasEl.width, data.canvasEl.height);  
  data.fileData = data.canvasEl.toDataURL("image/jpeg");  
  let hiddenLinks = document.querySelectorAll(".hidden\_links");  
  for (let hiddenLink of hiddenLinks) {  
    document.querySelector("body").remove(hiddenLink);  
  }  
  enableBtn("download");  
  enableBtn("share");  
}
```

## Download

We're using a hidden link to download the image. This technique involves creating a hidden anchor tag with a download attribute and programmatically clicking it. We start by removing any previously hidden links.

Then, we use the **DOM API** to add a new anchor element to the Document. We set the hypertext reference `href`to the data from the image and the `textContent` to `none` . We set the`target` to `_blank` , an unnamed window, because we are not navigating anywhere. We sent the download element to a filename. The download attribute tells the browser to respond to a click event by downloading the data in the `href` . By calling the anchor tag's click event, we trigger the download of the image.

```
function download() {  
  // cleanup any existing hidden links  
  let hiddenLinks = document.querySelectorAll(".hidden\_links");  
  for (let hiddenLink of hiddenLinks) {  
    document.querySelector("body").remove(hiddenLink);  
  }  
  
  if (data.fileData) {  
    let a = document.createElement("a");  
    a.classList.add("hidden-link");  
    a.href = data.fileData;  
    a.textContent = "";  
    a.target = "\_blank";  
    a.download = "photo.jpeg";  
    document.querySelector("body").append(a);  
    a.click();  
  }  
}
```

## Web Share

The **Share API** allows web applications to share images and text. This API doesn't work in Chrome Desktop but does work in Safari, Edge, and Android Chrome. You can see in the image below, taken from an instance of Safari on a Mac, that it will allow you to share the Snapshot to Mail, Message, AirDrop, Notes, Photos, Reminders, and Simulator. This is an instance where caniuse.com can help plan the feature set for an application.

## What's Next?

New features, JavaScript frameworks, and enhanced styling will help to build a better camera application. While the processing of composing an application using Web APIs can be an end in itself, it's also worth noting that this application is waiting for a step up.

### Modifying the Image

Offer options to modify the image drawn into the canvas element. This [example](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL#example_dynamically_change_images) shows you how to convert the image to grayscale.

### Modifying Exif Data

Use the [Geolocation API](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API) to get the latitude and longitude of the data. Then, a library like exif-js adds this data to the image before downloading or sharing it.

### Port the Code

Port the code from this camera application to React, Angular, or Vue. This is always a good exercise and will result in a better understanding of the state and logic.

### Style

Look for ways to improve the layout and usability of this application.

### Take Some Pictures

Use the camera you create. Find out if there are any features you would like to add and add them!