Enhance your React Native QR code scanning experience with a smooth, animated bounding box that highlights detected codes in real-time. This blog post explores how to achieve a visually polished and responsive scanning interface using expo-camera and react-native-reanimated. We'll dive into the code to see how it's done.
Introduction
A QR code scanner is a common feature in many mobile applications. While expo-camera provides the core functionality for scanning, adding an animated bounding box around detected QR codes significantly improves user feedback and the overall aesthetic. This component focuses on creating a dynamic and animated bounding box that appears, moves, and disappears gracefully as QR codes are detected or go out of view.
Technologies Used
This animated bounding box solution leverages two key React Native libraries:
expo-camera: This library is essential for accessing the device's camera and scanning QR codes in real time. It provides the underlying capability to detect the codes.react-native-reanimated: This powerful animation library is used to animate the bounding box. Specifically, it utilizes Reanimated'suseSharedValue,useAnimatedStyle,withSpring,withTiming,FadeInDown, andFadeOutDownto ensure the bounding box appears and moves smoothly without flickering or jank.
How it Works
The component operates by dynamically rendering and animating the bounding box when a QR code is detected. Here's a breakdown of the process:
- Camera Permission: The component first requests camera permissions from the user. If not granted, it prompts the user to grant permission.
- QR Code Detection:
expo-cameracontinuously scans for QR codes using theonBarcodeScannedprop. When a code is detected, it provides theBarcodeScanningResult, including thebounds(origin and size) of the detected QR code. - Dynamic Bounding Box Animation:
- Shared values (
x,y,boxWidth,boxHeight,opacity) are used to control the position, size, and visibility of the bounding box. - When a barcode is scanned, these shared values are updated using
withSpringfor smooth, spring-like animations to match the detected QR code'sbounds. AnextraSizeis added to the bounding box to make it slightly larger than the detected code, improving visibility. - The
opacityis animated to1when a code is detected and then animated back to0after a short delay, making the box fade out.
- Shared values (
- Displaying Scanned Data: If
barcodeScanningDatais available, anAnimated.Viewcontaining the scanned data (or children passed to the component) is rendered. This view usesFadeInDownandFadeOutDownlayout animations from Reanimated to appear and disappear gracefully, providing additional feedback to the user. - Resetting Box Size: After the
FadeOutDownanimation completes for the scanned data display, the bounding box's shared values (x,y,boxWidth,boxHeight) are reset to their initial states, preparing for the next scan.
This approach guarantees a responsive and visually polished scanning experience, minimizing any visual disruptions during transitions between detection states.
Code Snippets
Here's the core code for the QRScanner component, demonstrating the use of expo-camera and react-native-reanimated to create the animated bounding box.
First, the App.tsx file to render the QRScanner component:
1import { QRScanner } from "./";23export default function App() {4 return <QRScanner />;5}6
And here's the index.tsx containing the QRScanner component logic:
1import type { BarcodeScanningResult } from "expo-camera";2import { CameraView, useCameraPermissions } from "expo-camera";3import React, { useState } from "react";4import {5 Button,6 StyleSheet,7 Text,8 useWindowDimensions,9 View,10 ViewStyle,11} from "react-native";12import Animated, {13 FadeInDown,14 FadeOutDown,15 runOnJS,16 useAnimatedStyle,17 useSharedValue,18 withDelay,19 withSpring,20 withTiming,21} from "react-native-reanimated";2223export function QRScanner({24 onBarcodeScanned,25 onCameraReady,26 children,27 style,28}: {29 onBarcodeScanned?: CameraView["props"]["onBarcodeScanned"];30 onCameraReady?: () => void;31 children?: React.ReactNode;32 style?: ViewStyle;33}) {34 const [barcodeScanningData, setBarcodeScanningData] = useState<35 BarcodeScanningResult["data"] | null36 >(null);37 const { width, height } = useWindowDimensions();38 // Shared values for the bounding box39 const x = useSharedValue(width / 2);40 const y = useSharedValue(height / 2);41 const boxWidth = useSharedValue(0);42 const boxHeight = useSharedValue(0);43 const opacity = useSharedValue(0);4445 const handleBarCodeScanned = (scanningResults: BarcodeScanningResult) => {46 const { bounds } = scanningResults;47 if (bounds) {48 if (scanningResults.data !== barcodeScanningData) {49 setBarcodeScanningData(scanningResults.data);50 onBarcodeScanned?.(scanningResults);51 }52 x.value = withSpring(bounds.origin.x);53 y.value = withSpring(bounds.origin.y);54 boxWidth.value = withSpring(bounds.size.width);55 boxHeight.value = withSpring(bounds.size.height);56 opacity.value = withSpring(1, { duration: 300 }, (finished) => {57 if (finished) {58 opacity.value = withDelay(59 200,60 withTiming(0, { duration: 300 }, (finished) => {61 if (finished) {62 runOnJS(setBarcodeScanningData)(null);63 }64 })65 );66 }67 });68 } else {69 // Hide the rectangle after 2 seconds70 // opacity.value = withTiming(0, { duration: 300 });71 }72 };7374 const animatedStyle = useAnimatedStyle(() => {75 const extraSize = 0.1;76 return {77 position: "absolute",78 left: x.value - (boxWidth.value * extraSize) / 2,79 top: y.value - (boxHeight.value * extraSize) / 2,80 width: boxWidth.value * (1 + extraSize),81 height: boxHeight.value * (1 + extraSize),82 borderWidth: 2,83 borderStyle: "dashed",84 // a better color for a qr code scanner85 borderColor: "rgba(0,0,0,0.9 )",86 borderRadius: boxWidth.value / 10,87 opacity: opacity.value,88 };89 });9091 const [permission, requestPermission] = useCameraPermissions();9293 if (!permission || !permission.granted) {94 return (95 <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>96 <Button title='Request Camera Permission' onPress={requestPermission} />97 </View>98 );99 }100101 return (102 <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>103 <CameraView104 style={StyleSheet.absoluteFillObject}105 onBarcodeScanned={handleBarCodeScanned}106 onCameraReady={onCameraReady}107 mute108 />109 <Animated.View style={[animatedStyle, { alignItems: "center" }]}>110 {barcodeScanningData && (111 <Animated.View112 key={barcodeScanningData}113 entering={FadeInDown.springify().delay(200)}114 exiting={FadeOutDown.springify().withCallback((finished) => {115 //reset the box size to 0 after the animation is done116 if (finished) {117 x.value = width / 2;118 y.value = height / 2;119 boxWidth.value = 0;120 boxHeight.value = 0;121 }122 })}123 style={[124 {125 position: "absolute",126 top: "100%",127 marginTop: 10,128 backgroundColor: "rgba(0,0,0,0.5)",129 paddingVertical: 4,130 paddingHorizontal: 8,131 borderRadius: 20,132 minWidth: 200,133 },134 style,135 ]}>136 {children ?? (137 <Text138 style={{ color: "#fff", fontSize: 13 }}139 numberOfLines={1}140 adjustsFontSizeToFit>141 {barcodeScanningData}142 </Text>143 )}144 </Animated.View>145 )}146 </Animated.View>147 </View>148 );149}150151
Styling
The animatedStyle defines the visual properties of the bounding box. Key aspects include:
position: "absolute": Allows the bounding box to float over the camera view.left****,top****,width****,height: Dynamically set byuseAnimatedStylebased on the scanned QR code's bounds and animated using shared values.borderWidth****,borderStyle****,borderColor****,borderRadius: Styles the box visually, with a dashed border and dynamicborderRadiusfor a smoother look.opacity: Controls the visibility of the box, fading in and out withwithSpringandwithTiming.
The Animated.View for barcodeScanningData also includes styling for the data display, such as backgroundColor, padding, and borderRadius to create a clear, readable label for the scanned code.
Conclusion
Implementing an animated bounding box for your React Native QR code scanner elevates the user experience by providing clear, smooth visual feedback. By combining the real-time scanning capabilities of expo-camera with the fluid animations of react-native-reanimated, developers can create a professional and engaging scanning interface that feels polished and modern. This example demonstrates a robust solution for adding this engaging feature to your mobile applications.
