Algobook
- The developer's handbook
mode-switch
back-button
Buy Me A Coffee
Wed Jun 21 2023

Create a Webcam component for taking pictures for your web app

In this tutorial we will explore how we can access the webcam and take photos. We will then create a blob from the image taken, and make it downloadable and also prepare it for being sent to a backend API.

Create our component

First step is to create our component. In this guide, we will call it Webcam.tsx. We will also need a style sheet to make it look somewhat presentable, so we will create a file called Webcam.module.scss as well.

Guide on how to setup TypeScript and sass modules in your React application.

And then we will create our refs and our base component. We will need two refs for this, one that is responsible for the <video> and one for the canvas.

import { useRef } from "react"; import styles from "./Webcam.module.scss"; export const Webcam = () => { const videoRef = useRef<any>(null); const canvasRef = useRef<any>(null); return ( <div className={styles.cam}> <video className={styles.video} ref={videoRef} /> <canvas className={styles.canvas} ref={canvasRef} /> </div> ); };

Access the webcam

Now we want our webcam to start. For this, we will use navigator.mediaDevices. Let's create a useEffect hook that will trigger when the component is mounted, and start our video. Note that first time, the browser will ask for permission before it gets started.

useEffect(() => { navigator.mediaDevices .getUserMedia({ video: { width: 200, height: 200 } }) .then((stream) => { const video = videoRef.current; if (video) { video.srcObject = stream; video.play(); } }) .catch(alert); }, [videoRef]);

So what we are doing here is basically setting up a video stream with 200x200 dimensions and if it is allowed, we will stream it through our <video>.

Take a photo

Now we want to take a photo, and render it in our canvas. First, we will create a 2dcontext and then draw the image and create the buffer.

2d context and draw the image

We start with our context and image drawing. Create a function called onPhoto

const onPhoto = () => { const context = canvasRef.current.getContext("2d"); context.drawImage( videoRef.current, 0, 0, videoRef.current.clientWidth, videoRef.current.clientHeight ); };

And then we will create our buffer

const buffer = canvasRef.current.toDataURL("image/png");

Next, we will store the buffer in the state of our component so that we can use it for downloading and sending to the API.

Start by creating a new state in the component

export const Webcam = () => { const videoRef = useRef<any>(null); const canvasRef = useRef<any>(null); const [buffer, setBuffer] = useState("");

And then use the setBuffer to set the state

setBuffer(buffer);

Our full code of onPhoto should now look like this

const onPhoto = () => { const context = canvasRef.current.getContext("2d"); context.drawImage( videoRef.current, 0, 0, videoRef.current.clientWidth, videoRef.current.clientHeight ); const buffer = canvasRef.current.toDataURL("image/png"); setBuffer(buffer); };

Create our upload function

If we want to make our image able to be posted to an API, we can do so by converting our buffer to a blob and add to the form data.

Start by creating a converter function from buffer to blob.

const base64ToBlob = (base64: string) => { const byteCharacters = window.atob(base64); const byteNumbers = new Array(byteCharacters.length); for (let i = 0; i < byteCharacters.length; i++) { byteNumbers[i] = byteCharacters.charCodeAt(i); } const byteArray = new Uint8Array(byteNumbers); return new Blob([byteArray]); };

Then, create a new function called onUpload and add the buffer to the form data and call the API.

const onUpload = async () => { const formData = new FormData(); formData.append("myImage", base64ToBlob(buffer), "picture.png"); await fetch("API_URL", { method: "POST", body: formData, }); };

And that's it. We have a full guide on image uploads where we show this more detailed.

Add download link and buttons

Now it is time to trigger our functions. In our return statement, we will now render our download link and our upload button if our buffer is existing.

<div className={styles.container}> {buffer ? ( <> <a className={styles.button} href={buffer} download> Download </a> <button className={styles.button} onClick={onUpload}> Upload </button> </> ) : null} </div>

And then we will also render our take photo button

<button className={styles.button} onClick={onPhoto}> Take photo </button>

Full code of Webcam.tsx

The full component should now look something like this:

import { useEffect, useRef, useState } from "react"; import styles from "./Webcam.module.scss"; const base64ToBlob = (base64: string) => { const byteCharacters = window.atob(base64); const byteNumbers = new Array(byteCharacters.length); for (let i = 0; i < byteCharacters.length; i++) { byteNumbers[i] = byteCharacters.charCodeAt(i); } const byteArray = new Uint8Array(byteNumbers); return new Blob([byteArray]); }; export const Webcam = () => { const videoRef = useRef<any>(null); const canvasRef = useRef<any>(null); const [buffer, setBuffer] = useState(""); useEffect(() => { navigator.mediaDevices .getUserMedia({ video: { width: 200, height: 200 } }) .then((stream) => { const video = videoRef.current; if (video) { video.srcObject = stream; video.play(); } }) .catch(alert); }, [videoRef]); const onPhoto = () => { const context = canvasRef.current.getContext("2d"); context.drawImage( videoRef.current, 0, 0, videoRef.current.clientWidth, videoRef.current.clientHeight ); const buffer = canvasRef.current.toDataURL("image/png"); setBuffer(buffer); }; const onUpload = async () => { const formData = new FormData(); formData.append("myImage", base64ToBlob(buffer), "picture.png"); await fetch("API_URL", { method: "POST", body: formData, }); }; return ( <div className={styles.cam}> <video className={styles.video} ref={videoRef} /> <canvas className={styles.canvas} ref={canvasRef} /> <div className={styles.container}> {buffer ? ( <> <a className={styles.button} href={buffer} download> Download </a> <button className={styles.button} onClick={onUpload}> Upload </button> </> ) : null} <button className={styles.button} onClick={onPhoto}> Take photo </button> </div> </div> ); };

Styling

Now let's add some styles as well. In our Webcam.module.scss, add following style, or create your own cool styling to it 😇

.cam { display: flex; flex-direction: column; .video { height: 200px; width: 200px; } .canvas { margin-top: 1rem; min-height: 200px; min-width: 300px; max-height: 200px; max-width: 300px; } .container { display: flex; flex-direction: column; margin-top: 1rem; .button { display: flex; background-color: rgb(119, 119, 221); justify-content: center; text-decoration: none; border: none; padding: 12px 24px; margin-top: 2rem; white-space: nowrap; max-width: 100px; border-radius: 4px; letter-spacing: 0.05em; cursor: pointer; color: black; } } }

Consume it

And then in your application, wherever you want to use it, simply import it and render it as you normally do with your components

import { Webcam } from "components/webcam/Webcam"; <Webcam />;

And then you should see yourself on the screen, most probably 😃

Summary

In this tutorial, we created a React component for taking photos with our webcam. We then created a download link and a upload function for POSTing the image to an API.

I hope you enjoyed this guide, and thanks for reading.

All the best,

signatureWed Jun 21 2023
See all our articles