UNPKG

@thewirv/react-barcode-scanner

Version:

A React component for scanning QR codes and other barcodes via webcam

113 lines (108 loc) 4.34 kB
import { jsxs, jsx } from 'react/jsx-runtime'; import { useState, useMemo, useRef, useEffect } from 'react'; import { FiCameraOff } from 'react-icons/fi'; import { BrowserMultiFormatReader } from '@zxing/browser'; import { NotFoundException, ChecksumException, FormatException } from '@zxing/library'; const styles = { barcodeScannerError: { border: '8px #eee solid', borderRadius: '10px', padding: '2rem', }, barcodeScannerErrorSvg: { width: '75%', height: '75%', display: 'block', opacity: 0.2, margin: '0 auto', }, container: { width: '100%', paddingTop: '100%', overflow: 'hidden', position: 'relative', display: 'none', }, barcodeScannerVisible: { display: 'block', }, video: { top: 0, left: 0, width: '100%', height: '100%', display: 'block', overflow: 'hidden', position: 'absolute', transform: undefined, }, }; async function decodeBarcodeFromConstraints(codeReader, videoElement, { constraints, onSuccess, onError }) { if (!videoElement.current) return; try { const result = await codeReader.decodeOnceFromConstraints({ audio: false, video: constraints, preferCurrentTab: true }, videoElement.current); onSuccess(result.getText()); } catch (error) { if (error && !(error instanceof NotFoundException || error instanceof ChecksumException || error instanceof FormatException)) { onError(error); } } } function BarcodeScanner({ doScan = true, constraints = { facingMode: 'environment' }, onSuccess, onError, onLoad, Viewfinder, containerStyle, videoContainerStyle, videoStyle, videoProps: passedVideoProps, }) { const [isCameraInitialized, setIsCameraInitialized] = useState(false); const codeReader = useMemo(() => new BrowserMultiFormatReader(), []); const videoElement = useRef(null); const isShowingDisabledImage = !isCameraInitialized || !doScan; useEffect(() => { if (!doScan) return; if (!navigator?.mediaDevices) { const message = 'Your browser has no support for the MediaDevices API. You could fix this by running "npm i webrtc-adapter"'; console.warn(`[ReactBarcodeScanner]: ${message}`); onError(new Error(message)); return; } void decodeBarcodeFromConstraints(codeReader, videoElement, { constraints, onSuccess, onError, }); }, [onSuccess, onError, doScan, codeReader, constraints]); const videoProps = useMemo(() => { const onLoadedData = ({ nativeEvent }) => { const eventTarget = nativeEvent.target; if (!eventTarget?.readyState) return; if (eventTarget.readyState === eventTarget.HAVE_ENOUGH_DATA) { setIsCameraInitialized(true); onLoad?.(); } }; const defaultVideoProps = { playsInline: true, disablePictureInPicture: true, muted: true, onLoadedData, style: { ...styles.video, ...videoStyle, transform: `${videoStyle?.transform ?? ''} ${constraints.facingMode === 'user' ? 'scaleX(-1)' : ''}`, }, }; if (!passedVideoProps) return defaultVideoProps; if (typeof passedVideoProps !== 'function') return passedVideoProps; return passedVideoProps(defaultVideoProps); }, [constraints.facingMode, onLoad, passedVideoProps, videoStyle]); return (jsxs("section", { style: containerStyle, children: [isShowingDisabledImage && (jsx("div", { style: styles.barcodeScannerError, children: jsx(FiCameraOff, { size: 300, style: styles.barcodeScannerErrorSvg }) })), jsxs("div", { style: { ...styles.container, ...(!isShowingDisabledImage ? styles.barcodeScannerVisible : {}), ...videoContainerStyle, }, children: [jsx("video", { ref: videoElement, ...videoProps }), !!Viewfinder && jsx(Viewfinder, {})] })] })); } BarcodeScanner.displayName = 'BarcodeScanner'; export { BarcodeScanner };