Last active
April 13, 2023 14:05
-
-
Save BjoernRave/9e4c132b2f43dd514e9ba8e751a1914c to your computer and use it in GitHub Desktop.
(Next.js)React BarcodeScanner/QR Code Scanner
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//next.config.js | |
/** @type {import('next').NextConfig} */ | |
module.exports = { | |
experimental: { | |
urlImports: ['https://cdn.jsdelivr.net/npm/@undecaf/[email protected]/'], | |
}, | |
} | |
//in Next.js import like this | |
const BarcodeScanner = dynamic(() => import('@/components/BarcodeScanner'), { | |
ssr: false, | |
}) | |
//BarcodeScanner.tsx | |
import { BarcodeDetectorPolyfill } from '@undecaf/barcode-detector-polyfill' | |
import useTranslation from 'next-translate/useTranslation' | |
import { | |
FC, | |
useEffect, | |
useMemo, | |
useRef, | |
useState, | |
VideoHTMLAttributes, | |
} from 'react' | |
//add BarcodeDetector to typescript | |
declare global { | |
interface Window { | |
BarcodeDetector: any | |
} | |
} | |
try { | |
window['BarcodeDetector'].getSupportedFormats() | |
} catch { | |
window['BarcodeDetector'] = BarcodeDetectorPolyfill as any | |
} | |
const BarcodeScanner: FC<Props> = ({ | |
onScan, | |
formats = ['qr_code'], | |
className, | |
onError, | |
...props | |
}) => { | |
const { t } = useTranslation('common') | |
const videoRef = useRef(null) | |
const [error, setError] = useState<string>('') | |
const [stream, setStream] = useState(null) | |
const detector = useMemo( | |
() => new window.BarcodeDetector({ ...(formats && { formats }) }), | |
[formats] | |
) | |
const scanInterval = async (interval: any) => { | |
const barcodes = await detector.detect(videoRef.current) | |
if (barcodes.length === 0) { | |
return | |
} | |
clearInterval(interval) | |
const canvas = document.createElement('canvas') | |
canvas.width = videoRef.current.videoWidth | |
canvas.height = videoRef.current.videoHeight | |
canvas.getContext('2d').drawImage(videoRef.current, 0, 0) | |
const image = canvas.toDataURL('image/jpeg') | |
onScan(barcodes[0].rawValue, image) | |
} | |
useEffect(() => { | |
if (!videoRef.current) return | |
if ( | |
'mediaDevices' in navigator === false || | |
'getUserMedia' in navigator.mediaDevices === false | |
) { | |
onError() | |
setError('Ihr Browser unterstützt diese Funktion nicht.') | |
return | |
} | |
let interval | |
navigator.mediaDevices | |
.getUserMedia({ | |
audio: false, | |
video: { | |
advanced: [ | |
{ width: { exact: 2560 } }, | |
{ width: { exact: 1920 } }, | |
{ width: { exact: 1280 } }, | |
{ width: { exact: 1024 } }, | |
{ width: { exact: 900 } }, | |
{ width: { exact: 800 } }, | |
{ width: { exact: 640 } }, | |
{ width: { exact: 320 } }, | |
], | |
facingMode: { ideal: 'environment' }, | |
}, | |
}) | |
.then((res) => { | |
videoRef.current.srcObject = res | |
setStream(res) | |
interval = setInterval(async () => { | |
await scanInterval(interval) | |
}, 200) | |
}) | |
.catch((error) => { | |
//some browsers don't support setting exact widths, but iOS requires it | |
navigator.mediaDevices | |
.getUserMedia({ | |
audio: false, | |
video: { | |
facingMode: { ideal: 'environment' }, | |
}, | |
}) | |
.then((res) => { | |
videoRef.current.srcObject = res | |
setStream(res) | |
interval = setInterval(async () => { | |
await scanInterval(interval) | |
}, 200) | |
}) | |
.catch((error) => { | |
setError(error?.message) | |
onError() | |
console.error('erroasdr', error) | |
}) | |
}) | |
return () => { | |
clearInterval(interval) | |
} | |
}, [videoRef]) | |
useEffect(() => { | |
return () => { | |
if (stream) { | |
stream.getTracks().forEach((track) => track.stop()) | |
} | |
} | |
}, [stream]) | |
if (error) { | |
return ( | |
<div className='text-lg text-center text-red-500'> | |
{error === | |
'The request is not allowed by the user agent or the platform in the current context.' | |
? t('allowCameraAccess') | |
: error === 'Requested device not found' | |
? t('noCameraFound') | |
: error} | |
</div> | |
) | |
} | |
return ( | |
<video | |
ref={videoRef} | |
autoPlay={true} | |
playsInline={true} | |
muted={true} | |
{...props} | |
className={`w-full ${className}`} | |
/> | |
) | |
} | |
export default BarcodeScanner | |
interface Props extends VideoHTMLAttributes<HTMLVideoElement> { | |
onScan: (code: string, lastImage: string) => void | |
onError: () => void | |
formats?: string[] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment