在H5浏览器里实现扫描二维码(React hooks & ZXing)

6,468 阅读2分钟

之前为了使用扫描二维码的功能,还在考虑写一个微信小程序。但是项目推进到这一步时突然发现微信小程序想要对接外部服务器时,服务器必须使用一个已经备案的域名(而不能是一个未备案的域名或IP地址)于是在感叹项目要崴了的时候,想到我可以先生成一个二维码,通过微信扫一扫打开我的网站的一个页面,然后再通过这个页面的扫描二维码来添加我自己的逻辑(比如登录参数等)。

我首先找到了一个Instascan库,但是这个库对于版本47之后的Chrome有一个问题,即对于video元素不再使用createObjectURL的方式来绑定视频流了。后来找到了老牌的ZXing Library的JS port。这是一个非常高性能的用来扫描barcode和QRcode的库,你也可以在repo中看到各种示例用法(包括在AngularJS和Vue中的)。一个晚上就搞定了,从此不需要再看微信脸色。

用法也非常简单,只需要先初始化视频流设备,然后触发一个decode方法就可以了。

准备:

npm i -S @zxing/library

以下是代码


import React, {useState, useEffect} from 'react';
import {Button} from 'reactstrap';
import { BrowserQRCodeReader } from '@zxing/library';

function initReader(){
  const codeReader = new BrowserQRCodeReader();

  return codeReader.getVideoInputDevices()
  .then(videoInputDevices => {
    if (videoInputDevices.length <= 0){
      throw Error('妹找到摄像头啊');
    }
    return {
      videoDeviceID: videoInputDevices[0].deviceId,
      codeReader
    }
  })
  .catch(err => {
    console.log(err);
  })
}

function App() {

  const [reader, setReader] = useState();
  const [deviceID, setDeviceID] = useState();

  const [message, setMessage] = useState();
  const [result, setResult] = useState();

  useEffect(() => {
    (async function (){
      if(!reader){
        const {videoDeviceID, codeReader} = await initReader();
        setReader(codeReader);
        setDeviceID(videoDeviceID);
      }
    })()
  }, [reader]);

  const decode = (codeReader, selectedDeviceId) => {
    codeReader.decodeFromInputVideoDevice(selectedDeviceId, 'video')
    .then((result) => {
      //console.log(result);
      setResult(result.text);
    }).catch((err) => {
      setMessage(err.toString());
    })
  }

  return <div className="App">
  
    <div className="container" style={{textAlign:'center', width:'100%'}}>
      <div>
        <video id="video" width="200" height="200" style={{border: '1px solid gray', margin:'30px'}} />
        <Button style={{margin:'30px'}} size="lg" color="primary" onClick={() => decode(reader, deviceID)}>扫一扫</Button>
      </div>
    </div>
    <div>{result}</div>
    <div>{message}</div>
  </div>
}

export default App;