How to Build a Barcode and QR Code Scanner with Solidjs

Solidjs is a web framework like React. Instead of using techniques like virtual DOM, it compiles its templates to real DOM nodes and updates them with fine-grained reactions.

In this article, we are going to build a barcode and QR code scanner using Solidjs. Dynamsoft Camera Enhancer is used to access the camera in browsers and Dynamsoft Barcode Reader is used to read barcodes from the camera video frames.

Getting started with Dynamsoft Barcode Reader

DOWNLOAD THE SDK WITH A 30-DAY LICENSE
REQUEST A 30-DAY LICENSE

Build a Barcode and QR Code Scanner with Solidjs in Steps

Let’s do this in steps.

New Project

Create a new Solidjs TypeScript project named barcode-scanner:

npx degit solidjs/templates/ts barcode-scanner

Then, we can run the following to test it:

cd barcode-scanner
npm install
npm run dev

Install Dependencies

Install Dynamsoft Barcode Reader and Dynamsoft Camera Enhancer.

npm install dynamsoft-javascript-barcode@9.6.2 dynamsoft-camera-enhancer

Create a Scanner Component

Next, let’s create a scanner component under components/Scanner.tsx that can open the camera and scan barcodes from the camera video frames.

  1. Define the UI element of the scanner and bind the container to cameraContainer.

    let camera:CameraEnhancer;
    return (
      <div ref={cameraContainer} class="container" style="display:none;">
        <svg class="dce-bg-loading" viewBox="0 0 1792 1792"><path d="M1760 896q0 176-68.5 336t-184 275.5-275.5 184-336 68.5-336-68.5-275.5-184-184-275.5-68.5-336q0-213 97-398.5t265-305.5 374-151v228q-221 45-366.5 221t-145.5 406q0 130 51 248.5t136.5 204 204 136.5 248.5 51 248.5-51 204-136.5 136.5-204 51-248.5q0-230-145.5-406t-366.5-221v-228q206 31 374 151t265 305.5 97 398.5z"/></svg>
        <svg class="dce-bg-camera" viewBox="0 0 2048 1792"><path d="M1024 672q119 0 203.5 84.5t84.5 203.5-84.5 203.5-203.5 84.5-203.5-84.5-84.5-203.5 84.5-203.5 203.5-84.5zm704-416q106 0 181 75t75 181v896q0 106-75 181t-181 75h-1408q-106 0-181-75t-75-181v-896q0-106 75-181t181-75h224l51-136q19-49 69.5-84.5t103.5-35.5h512q53 0 103.5 35.5t69.5 84.5l51 136h224zm-704 1152q185 0 316.5-131.5t131.5-316.5-131.5-316.5-316.5-131.5-316.5 131.5-131.5 316.5 131.5 316.5 316.5 131.5z"/></svg>
        <div class="dce-video-container"></div>
        <div class="dce-scanarea">
          <div class="dce-scanlight"></div>
        </div>
        <div class="sel-container">
          <select class="dce-sel-camera"></select>
          <select class="dce-sel-resolution"></select>
        </div>
        {props.children}
      </div>
    );
    

    Create a css file with the following content and then import it in the tsx file. The camera container will take up the whole screen.

    @keyframes dce-rotate{from{transform:rotate(0turn);}to{transform:rotate(1turn);}}
    @keyframes dce-scanlight{from{top:0;}to{top:97%;}}
    .container{width:100%;height:100%;min-width:100px;min-height:100px;background:#eee;position:absolute;left:0;top:0;}
    .dce-bg-loading{animation:1s linear infinite dce-rotate;width:40%;height:40%;position:absolute;margin:auto;left:0;top:0;right:0;bottom:0;fill:#aaa;}
    .dce-bg-camera{display:none;width:40%;height:40%;position:absolute;margin:auto;left:0;top:0;right:0;bottom:0;fill:#aaa;}
    .dce-video-container{position:absolute;left:0;top:0;width:100%;height:100%;}
    .dce-scanarea{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none;}
    .dce-scanarea .dce-scanlight{display:none;position:absolute;width:100%;height:3%;border-radius:50%;box-shadow:0px 0px 2vw 1px #00e5ff;background:#fff;animation:3s infinite dce-scanlight;user-select:none;}
    .sel-container{position: absolute;left: 0;top: 0;}
    .sel-container .dce-sel-camera{display:block;}
    .sel-container .dce-sel-resolution{display:block;margin-top:5px;}
    .sel-container {display:block;margin-top:5px;}
    
  2. Initialize Barcode Reader and Camera Enhancer when the component is mounted.

    import { CameraEnhancer } from 'dynamsoft-camera-enhancer';
    import { BarcodeReader } from 'dynamsoft-javascript-barcode';
    import { onMount } from 'solid-js';
       
    BarcodeReader.engineResourcePath = "https://cdn.jsdelivr.net/npm/dynamsoft-javascript-barcode@9.6.2/dist/";
    const Scanner: Component = () => {
      let camera:CameraEnhancer;
      let reader:BarcodeReader;
      onMount(async () => {
        if (!camera) {
          BarcodeReader.license = "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ=="; // trial license
          camera = await CameraEnhancer.createInstance();
          if (cameraContainer) {
            await camera.setUIElement(cameraContainer);
          }
          reader = await BarcodeReader.createInstance();
        }
      });
    }
    
  3. Define props for the scanner component:

    import { TextResult } from 'dynamsoft-javascript-barcode/dist/types/interface/textresult';
    import { JSX } from 'solid-js';
    export interface ScannerProps {
      initialized?: () => void;
      onScanned?: (results:TextResult[]) => void;
      active:boolean;
      children?: JSX.Element;
    }
    

    There are two events. The initialized event is triggered when the camera enhancer and barcode reader is initialized and the onScanned event is triggered when at least one barcode is found.

    The active prop is used to control whether the camera is open or not.

    Then, update the component to use the props:

    - const Scanner: Component = () => {
    + const Scanner: Component<ScannerProps> = (props:ScannerProps) => {
    
  4. Call the initialized event when the initialization is done.

    onMount(async () => {
      if (!camera) {
        BarcodeReader.license = "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ=="; // trial license
        camera = await CameraEnhancer.createInstance();
        if (cameraContainer) {
          await camera.setUIElement(cameraContainer);
        }
        reader = await BarcodeReader.createInstance();
      }
      if (props.initialized) {
        props.initialized();
      }
    });
    
  5. Define functions related to decoding.

    let interval:any;
    let decoding = false;
    const startDecoding = () => {
      if (interval) {
        clearInterval(interval);
      }
      decoding = false;
      interval = setInterval(captureAndDecode,100); //set an interval to read barcodes from camera video frames.
    }
    
    const stopDecoding = () => {
      if (interval) {
        clearInterval(interval);
      }
      decoding = false;
    }
        
    const captureAndDecode = async () => {
      if (!camera || !reader) {
        return
      }
      if (camera.isOpen() === false) {
        return;
      }
      if (decoding == true) {
        return;
      }
      let frame = camera.getFrame();
      if (frame) {
        decoding = true; // set decoding to true so that the next frame will be skipped if the decoding has not completed.
        let results = await reader.decode(frame);
        if (results.length>0) {
          stopDecoding();
          if (props.onScanned) {
            props.onScanned(results);
          }
        }
        decoding = false;
      }
    };
    
  6. Use createEffect to monitor whether the active props is changed. If it is set to true, open the camera and start decoding. Otherwise, stop decoding and close the camera.

    createEffect(() => {
      const {active} = props; //deconstruct, see: https://github.com/solidjs/solid/discussions/749
      if (camera) {
        if (active === true) {
          camera.open(true);
          startDecoding();
        }else{
          stopDecoding();
          camera.close(true);
        }
      }
    });
    

Use the Scanner Component in the App

Let’s modify App.tsx to use the scanner component. The major code is like the following:

const [active,setActive] = createSignal(false);
const [initialized,setInitialized] = createSignal(false);
const [barcodeResults,setBarcodeResults] = createSignal([] as TextResult[]);
const startScan = () => {
  setActive(true);
}

const stopScan = () => {
  setActive(false);
}

const onScanned = (results:TextResult[]) => {
  setActive(false);
  setBarcodeResults(results);
}

return (
  <div class={styles.App}>
    <h1>Solidjs Barcode Scanner Demo</h1>
    <Show when={initialized()} fallback={<div>Initializing...</div>}>
      <button onClick={startScan}>Start Scanning</button>
    </Show>
    <Scanner 
      active={active()}  
      onScanned={(results)=> {onScanned(results)}}
      initialized={()=> {setInitialized(true)}} 
    >
      <button class={styles.CloseButton} onClick={stopScan}>Close</button>
    </Scanner>
    <For each={barcodeResults()}>
      {(result) => <div>{result.barcodeFormatString+": "+result.barcodeText}</div>}
    </For>
  </div>
);

All right, we’ve now finished the barcode and QR code scanner with Solidjs. You can check out the online demo to have a try.

Source Code

https://github.com/tony-xlh/solidjs-barcode-qrcode-scanner