Skip to content

Instantly share code, notes, and snippets.

@BjoernRave
Last active April 13, 2023 14:05
Show Gist options
  • Save BjoernRave/9e4c132b2f43dd514e9ba8e751a1914c to your computer and use it in GitHub Desktop.
Save BjoernRave/9e4c132b2f43dd514e9ba8e751a1914c to your computer and use it in GitHub Desktop.
(Next.js)React BarcodeScanner/QR Code Scanner
//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