Crafting a Camera Component for Identification Cards
As I embarked on building a front-end camera component, I encountered a unique challenge: creating a seamless experience for users to upload images of their identification cards to a back-end service. In this article, I’ll guide you through the process of configuring a live media stream, capturing snapshots with React Hooks, and styling elements using styled-components.
Stream Configuration
To access the browser navigator and display a live video feed from the user’s camera, we’ll invoke the getUserMedia()
method, passing a configuration object that defaults to the rear-facing camera on mobile devices and doesn’t require audio. This method requests permission from the user to access the media defined in the configuration, returning a promise that resolves with a MediaStream
object or rejects with an error.
Using React’s useEffect()
Hook, we’ll create and store the requested stream if none exists, or return a cleanup function to prevent memory leaks when the component unmounts. We can abstract this logic into a custom Hook, taking the configuration object as an argument, creating the cleanup function, and returning the stream to the camera component.
Positioning the Video Feed
To enhance the user experience, we’ll position the video within the component to resemble an identification card. This requires maintaining a landscape ratio, regardless of the native resolution of the camera. We can calculate a ratio ≥ 1 by dividing by the largest dimension. Once the video is available for playback, we’ll evaluate the native resolution of the camera and use it to calculate the desired aspect ratio of the parent container.
To make the component responsive, we’ll use react-measure
to notify the component whenever the width of the parent container changes, recalculating the height accordingly. We can abstract the ratio calculation into a custom Hook, returning both the calculated ratio and setter function.
Capturing and Clearing the Image
To emulate a camera snapshot, we’ll position a <canvas/>
element on top of the video with matching dimensions. When the user initiates a capture, the current frame in the feed will be drawn onto the canvas, temporarily hiding the video. We’ll create a two-dimensional rendering context on the canvas, drawing the current frame of the video as an image, and exporting the resulting Blob as an argument in a handleCapture()
callback.
To discard the image, we’ll revert the canvas to its initial state via a handleClear()
callback, retrieving the same drawing context instance and passing the canvas’s width and height to the clearRect()
function.
Styling the Component
With the ability to capture an image, we’ll implement a card-aid overlay, a flash animation on capture, and style the elements using styled-components. The overlay component will feature a white, rounded border layered on top of the video, encouraging the user to fit their identification card within the boundary. The flash component will have a solid white background, layered on top of the video, with a keyframe animation that briefly sets the opacity to 0.75 before reducing it back to zero.
We’ll pass the resolution of the camera as props to the parent container, determining its maximum width and height, and add a local state variable to keep the video and overlay elements hidden until the camera begins streaming.
The Future of Image Capture
For now, this component serves as proof of authenticity, used alongside a form where users manually input field information from the identification cards. I’m excited to explore the possibility of integrating OCR technology to scrape the fields from the images, eliminating the need for the form altogether.
Thanks for joining me on this journey, and special thanks to Pete Correia for reviewing the component code.