Take a Selfie with React – Access Web Camera JS

Take a Selfie with React – Access Web Camera JS

Are you looking for a solution to take pictures from your react application? You are in the right place. In this post, I will teach you how to take a selfie from desktop (web camera js), mobile or iPad with react by using JavaScript getUserMedia API.

Steps to Create the Selfie App

JavaScript has a built-in API called MediaDevices to access the input devices like web camera, microphone, or even screen–sharing.

  • We will access the web camera JS data using getUserMedia method of MediaDevices object
  • Transfer the data from getUserMedia to the HTML5 video element.
  • Take a picture by grabbing the current video frame and draw it in the canvas element.
  • Save the picture using anchor element’s download attribute.

Create a component Selfie.js

Declare a state object for the component with a property imageURL to store the URL of the captured image.

Declare 3 properties videoEle, canvasEle and imageEle by assigning the Refs. Refs are used to accessing the DOM elements or React elements created inside the render method.

To assign a reference, you should use createRef method of React object

import React, { Component } from 'react';


class Selfie extends Component {
    state = {
        imageURL: ''
    }

    videoEle = React.createRef();
    canvasEle = React.createRef();
    imageEle = React.createRef();

    render() {
      return null;
    }
}

export default Selfie;

Let’s write the JSX for our selfie component, we are going to add three elements video, canvas and image elements.

render() {
   return (<div className="selfie">
            <video ref={this.videoEle}></video>
            <canvas ref={this.canvasEle} style={{display: 'none'}}> 
            </canvas>
            <img src={this.state.imageURL} ref={this.imageEle} />
        </div>)
}

The ref attribute is used to map the DOM element with our properties in the component. We have created a hidden canvas element, as it’s sole purpose is to capture the video frame using JavaScript.

Start the Web Camera JS or Mobile Camera

Create a method startCamera to start the camera using navigator.mediaDevices.getUserMedia(constraints) method. The constraints parameter is an object used to define what hardware or the values we want to listen. For example, {video: true, audio: true} will listen for both video and audio stream from the web camera js. In our case, we need to listen to the video stream only.

  startCamera = async () => {
        try {
            const stream =  await navigator.mediaDevices.getUserMedia({
                video: true
            });

            this.videoEle.current.srcObject = stream;
            
        } catch(err) {
            console.log(err);
        }
    }

The getUserMedia method returns a stream object. We can assign that stream value to the video element’s srcObject to stream the data from the camera to the Video element.

Add a componentDidMount lifecycle hook, inside the life cycle method invoke the startCamera method.

    componentDidMount = async () => {
        this.startCamera();
    }

Now, run the app npm run start, you can see your face on the web application by accessing web camera JS.

Stop Web Camera / Mobile Camera

We need a method to stop the web camera, add the following method

    stopCam = () => {
        const stream = this.videoEle.current.srcObject;
        const tracks = stream.getTracks();
        
        tracks.forEach(track => {
          track.stop();
        });
    }

We are accessing the tracks of the video and stop it one by one inside the forEach loop.

Take Selfie from Web Camera or Mobile Camera

Create a method takeSelfie. In this method, we are going to use the drawImage method in the canvas context to draw the current video frame on the canvas.

    takeSelfie = async () => {
        // Get the exact size of the video element.
        const width = this.videoEle.current.videoWidth;
        const height = this.videoEle.current.videoHeight;

        // get the context object of hidden canvas
        const ctx = this.canvasEle.current.getContext('2d');

        // Set the canvas to the same dimensions as the video.
        this.canvasEle.current.width = width;
        this.canvasEle.current.height = height;

        // Draw the current frame from the video on the canvas.
        ctx.drawImage(this.videoEle.current, 0, 0, width, height);

        // Get an image dataURL from the canvas.
        const imageDataURL = this.canvasEle.current.toDataURL('image/png');

        // Set the dataURL as source of an image element, showing the captured photo.
        this.stopCam();
        this.setState({
            imageURL: imageDataURL
        })
    }

We are making a dataURL using toDataURL method in canvas. Later, we are updating the state property imageURL with imageDataURL, and before that stop the cam by invoking the this.stopCam() method. Because, of the state change, the component will rerender to show the preview of the image we have taken.

Add Buttons to Take Selfie and Download Selfie

We need to rewrite our JSX to have buttons to take a selfie and download that selfie. We are also going to style our component to look better.

    render() {
        return (<div className="selfie">
            <div className="cam">
                <video width="100%" height="100%" className="video-player" autoPlay={true} ref={this.videoEle}></video>
                <button className="btn capture-btn" onClick={this.takeSelfie}>
                    <i class="fa fa-camera" aria-hidden="true"></i>
                </button>
            </div>


            <canvas ref={this.canvasEle} style={{display: 'none'}}></canvas>
            <div className="preview">
                <img className="preview-img" src={this.state.imageURL} ref={this.imageEle} />

                <div className="btn-container">
                    <button className="btn back-btn" onClick={this.backToCam}>
                        <i class="fa fa-chevron-left" aria-hidden="true"></i>
                    </button>
                    <a href={this.state.imageURL} download="selfie.png"
                     className="btn download-btn">
                        <i class="fa fa-download" aria-hidden="true"></i>
                    </a>
                </div>

            </div>

        </div>)
    }

Now, we have separate container div for video element and image elements. Each of the div has some buttons. For the video element, there’s a button to capture the selfie. By clicking on that button, the this.takeSelfie() method will get invoked to take the picture.

Inside the image container div, there are two buttons, one is to take you back to the cam by invoking this.backToCam method, another one is the anchor element to download the captured image.

The anchor element has a download attribute with the value selfie.png, the href attribute is updated with the imageURL property of the state. So that we can download the captured image by clicking this anchor element.

Add Back Button Method

Inside the component, add the following method.

    backToCam = () => {
        this.setState({
            imageURL: ''
        }, () => {
            this.startCamera();
        })
    }

We are assigning an empty string to the imageURL and then start the web camera to take another picture.

Show/Hide Web Camera JS / Mobile Camera

To show/hide the web camera and the preview image, we can use the imageURL property in the state. If it’s an empty string, then we need to display the video web camera, otherwise, we can show the preview image.

   render() {
        return (<div className="selfie">
            {this.state.imageURL === '' && <div className="cam">
                <video width="100%" height="100%" className="video-player" autoPlay={true} ref={this.videoEle}></video>
                <button className="btn capture-btn" onClick={this.takeSelfie}>
                    <i class="fa fa-camera" aria-hidden="true"></i>
                </button>
            </div>
            }


            <canvas ref={this.canvasEle} style={{display: 'none'}}></canvas>
            {this.state.imageURL !== '' && <div className="preview">
                <img className="preview-img" src={this.state.imageURL} ref={this.imageEle} />

                <div className="btn-container">
                    <button className="btn back-btn" onClick={this.backToCam}>
                        <i class="fa fa-chevron-left" aria-hidden="true"></i>
                    </button>
                    <a href={this.state.imageURL} download="selfie.png"
                     className="btn download-btn">
                        <i class="fa fa-download" aria-hidden="true"></i>
                    </a>
                </div>

            </div>
            }

        </div>)
    }

Our final render method is having a condition to show/hide video player and preview image based on imageURL property.

Style our component

Add the following font-awesome CDN in your index.css file

@import "https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css";

Create a file Selfie.css, add the following CSS into it.

.selfie {
    display: flex;
    justify-content: center;
}

.cam, .preview {
    position: relative;
}

.preview-img {
    display: block;
}

.capture-btn {
    position: absolute;
    bottom: 9px;
    display: block;
    left: 50%;
    transform: translateX(-50%);
    background: none;
    color: #fff;
    border: none;
    font-size: 40px;
}

.video-player {
    display: block;
}

.btn-container {
    display: flex;
    justify-content: space-between;
    position: absolute;
    bottom: 0;
    left: 0;
}

.back-btn, .download-btn {
    background: none;
    color: #fff;
    border: none;
    font-size: 40px;
}

.download-btn {
    margin-left: 25px;
}

import the Selfie.css in your Selfie.js

import './Selfie.css';

Complete Code of Our Selfie Component

import React, { Component } from 'react';

import './Selfie.css';

class Selfie extends Component {
    state = {
        imageURL: '',
    }

    videoEle = React.createRef();
    canvasEle = React.createRef();
    imageEle = React.createRef();

    componentDidMount = async () => {
        this.startCamera();
    }

    startCamera = async () => {
        try {
            const stream =  await navigator.mediaDevices.getUserMedia({
                video: true
            });

            this.videoEle.current.srcObject = stream;
            
        } catch(err) {
            console.log(err);
        }
    }


    takeSelfie = async () => {
        // Get the exact size of the video element.
        const width = this.videoEle.current.videoWidth;
        const height = this.videoEle.current.videoHeight;

        // get the context object of hidden canvas
        const ctx = this.canvasEle.current.getContext('2d');

        // Set the canvas to the same dimensions as the video.
        this.canvasEle.current.width = width;
        this.canvasEle.current.height = height;

        // Draw the current frame from the video on the canvas.
        ctx.drawImage(this.videoEle.current, 0, 0, width, height);

        // Get an image dataURL from the canvas.
        const imageDataURL = this.canvasEle.current.toDataURL('image/png');
        this.stopCam();

        this.setState({
            imageURL: imageDataURL
        })
    }

    stopCam = () => {
        const stream = this.videoEle.current.srcObject;
        const tracks = stream.getTracks();
        
        tracks.forEach(track => {
          track.stop();
        });
    }

    backToCam = () => {
        this.setState({
            imageURL: ''
        }, () => {
            this.startCamera();
        })
    }

    

    render() {
        return (<div className="selfie">
            {this.state.imageURL === '' && <div className="cam">
                <video width="100%" height="100%" className="video-player" autoPlay={true} ref={this.videoEle}></video>
                <button className="btn capture-btn" onClick={this.takeSelfie}>
                    <i class="fa fa-camera" aria-hidden="true"></i>
                </button>
            </div>
            }


            <canvas ref={this.canvasEle} style={{display: 'none'}}></canvas>
            {this.state.imageURL !== '' && <div className="preview">
                <img className="preview-img" src={this.state.imageURL} ref={this.imageEle} />

                <div className="btn-container">
                    <button className="btn back-btn" onClick={this.backToCam}>
                        <i class="fa fa-chevron-left" aria-hidden="true"></i>
                    </button>
                    <a href={this.state.imageURL} download="selfie.png"
                     className="btn download-btn">
                        <i class="fa fa-download" aria-hidden="true"></i>
                    </a>
                </div>

            </div>
            }

        </div>)
    }
}

export default Selfie;

Conclusion

We made it, you learned to create a selfie app with React. You have learned how to access the web camera and mobile camera using MediaDevices API in JavaScript, how to take selfie using canvas and save it to your local device using anchor tag. If you tried this method, share the repo in the comment. If you have other suggestions, please make a comment below.