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
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.
-
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;}
-
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(); } }); }
-
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 theonScanned
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) => {
-
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(); } });
-
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; } };
-
Use
createEffect
to monitor whether theactive
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.