GrCodeScanner
Overview
When to take it
- signing in with a QR — on the login screen of another device, instead of typing a code by hand;
- a product in a warehouse — the receiving desk scans barcodes one after another, and
continuousis exactly for that; - a payment or a link from a poster — the code leads to a page, and the application opens it;
- pairing a device — the code is shown by a second screen, and the phone confirms it.
When to take something else
| Need | Take |
|---|---|
| Take a photo rather than read a code | GrCameraCapture |
| Choose a crop out of a ready picture | GrImageCrop |
| Accept a file with a shot of a code | GrFileUpload |
Examples 3 each with a live demo and its code
Receiving cartons against an order
continuouskeeps the scanner running and reports the same code again — identical cartons in a row are legitimate repeats, and stopping after each would cost more than the scan.formatsnarrows the search to EAN-13 and Code 128,intervalsets how often a frame is read, and a code that is not on the order lands in its own line.Receiving · PO-77120Northwind Supplies → Hamburg, dock 3In progressThe camera is off.
Desk lamp, black400638133393124 / 24Desk lamp, white400638133394817 / 24Monitor arm, single59012341234570 / 12Cable tray, 80 cm761234567890440 / 40Scan cartons in any order; repeats count.Linking a phone with a QR code
One scan is one answer, so the scanner stops at the first find.
detectbrings the value and the symbology together: a link and an order number look alike as strings, and the page checks both before linking anything. The camera starts on a press — a permission request without a user action is refused on sight.Link this phoneOpen Settings → Devices on your computer and scan the QR code shown there The camera starts when you press the button.The camera is off.
Check-in at the door, with its own panel
The
controlsslot replaces the panel and leaves the stream where it was: permission, the frame loop and cleanup stay the component’s job. The page decides what the one big button says and words every state — «Camera blocked — allow it in the address bar» helps a volunteer more than «denied».Hall B · check-in1 of 320 admitted The camera is off.
Camera off
API generated from the package · cannot drift
Props
| Prop | Type | default | Description |
|---|---|---|---|
facing | "user" | "environment" | undefined | "environment" | Which camera: `environment` — the rear one, the one that scans. |
deviceId | string | undefined | undefined | A specific device, if the application has already picked one. |
autoStart | boolean | undefined | false | Turn the camera on right away. `false` by default: a permission prompt without a user action is dismissed unseen, and the browser will not ask a second time. |
size | GrCodeScannerSize | undefined | undefined | The size step: the controls and the type size of the captions. `md` by default; taken from `GrConfigProvider`. |
disabled | boolean | undefined | false | The scanner is unavailable: the camera does not turn on, the buttons are disabled. |
ariaLabel | string | undefined | undefined | The name of the group with the preview and the buttons. Not set — from the locale ("Code scanner"). |
formats | readonly string[] | undefined | undefined | Symbologies: `qr_code`, `ean_13`, `code_128`, … Without a list — everything the browser can do. |
detector | GrCodeDetector | undefined | undefined | A detector for browsers without `BarcodeDetector` — Safari and Firefox. The package carries no decoder of its own: it has not a single dependency, and one component would bring in the heaviest of them. The recipe for wiring up a ready-made library is on the component page. |
interval | number | undefined | 250 | How often to parse a frame, ms. More often — a hot device, less often — a sluggish response. |
continuous | boolean | undefined | false | Report the same code again: goods receiving scans one after another. |
Slots
| Slot | Type | Description |
|---|---|---|
controls | { status: GrCameraStatus; start: () => void; stop: () => void; } | Your own control panel instead of the built-in one. |
Events
| Event | Signature | Description |
|---|---|---|
start | (event: "start"): void | — |
stop | (event: "stop"): void | — |
statusChange | (event: "statusChange", status: GrCameraStatus): void | — |
detect | (event: "detect", codes: GrCodeResult[]): void | — |
Methods
| Methods | Signature | Description |
|---|---|---|
start | () => Promise<void> | Request permission, turn the camera on and start parsing frames. Does nothing with `disabled`, during a request or on a live stream; a refusal settles in `status`. |
stop | () => void | Stop parsing frames and turn the camera off; the state returns to `idle`. |
status | GrCameraStatus | The current state of the camera (`GrCameraStatus`): `idle`, `starting`, `live` or the reason for a refusal. |
Types
Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.
import type { GrCameraStatus, GrCodeResult, GrCodeDetector } from '@feugene/granularity-media/components/GrCodeScanner'Filedist/types/components/shared/cameraState.d.ts
type GrCameraStatus = 'idle'Filedist/types/components/GrCodeScanner/codeDetection.d.ts
interface GrCodeResult {
value: string;
format: string;
}type GrCodeDetector = (source: CanvasImageSource) => Promise<GrCodeResult[]>;
export declare function nativeDetectorSupported(scope?: typeof globalThis): boolean;
export declare function createNativeDetector(formats: readonly string[] | undefined, scope?: typeof globalThis): GrCodeDetector | null;
export declare function freshCodes(previous: readonly GrCodeResult[], next: readonly GrCodeResult[], continuous: boolean): GrCodeResult[];function nativeDetectorSupported(scope?: typeof globalThis): boolean;function createNativeDetector(formats: readonly string[] | undefined, scope?: typeof globalThis): GrCodeDetector | null;function freshCodes(previous: readonly GrCodeResult[], next: readonly GrCodeResult[], continuous: boolean): GrCodeResult[];Filedist/types/components/GrCodeScanner/config.d.ts
const grCodeScannerConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrCodeScanner">;Filedist/types/components/GrCodeScanner/defaults.d.ts
interface GrCodeScannerConfigurableProps {
size?: GrCodeScannerSize;
}Filedist/types/components/GrCodeScanner/GrCodeScanner.vue.d.ts
interface GrCodeScannerProps {
facing?: 'user' | 'environment';
deviceId?: string;
autoStart?: boolean;
formats?: readonly string[];
detector?: GrCodeDetector;
interval?: number;
continuous?: boolean;
size?: GrCodeScannerSize;
disabled?: boolean;
ariaLabel?: string;
}interface GrCodeScannerEmits {
(e: 'detect', codes: GrCodeResult[]): void;
(e: 'start'): void;
(e: 'stop'): void;
(e: 'statusChange', status: GrCameraStatus): void;
}Filedist/types/components/GrCodeScanner/grCodeScannerStyles.d.ts
type GrCodeScannerSize = 'xs' | 'sm' | 'md' | 'lg';const grCodeScannerSafelist: string[];Usage from the library prose
The package does not carry a decoder
The native BarcodeDetector exists in Chrome and Edge, but it is in neither Safari nor Firefox —
that is, on an iPhone it does not exist at all, while scanning is most often done precisely with a
phone. Embedding a decoder into the package is not possible: it has not a single dependency, and for
the sake of one component the heaviest one would appear — and for everyone, including those who
took only the cropper.
The component therefore handles the native path itself, and the other browsers are covered by a detector the application passes in:
import { BrowserMultiFormatReader } from '@zxing/browser'
const reader = new BrowserMultiFormatReader()
async function detector(source) {
const canvas = Object.assign(document.createElement('canvas'), {
width: source.videoWidth,
height: source.videoHeight,
})
canvas.getContext('2d')?.drawImage(source, 0, 0)
const result = reader.decodeFromCanvas(canvas)
return result ? [{ value: result.getText(), format: String(result.getBarcodeFormat()) }] : []
}<GrCodeScanner :detector="detector" />The prop is deliberately stronger than the native path: an application that has connected a library of its own usually does so for the sake of a format the native detector does not know.
There being nothing to read with is a separate state: the component says straight out that the
browser cannot do it and that a detector is needed. Offering “switch the camera on” in that case
would mean sending the user to solve the wrong task.
This is checked after mounting: on the server there is never a BarcodeDetector, and a decision
made in the render would diverge from the browser during hydration. The first render is the same
everywhere — the idle state — and the message “the browser cannot read codes” appears only where it
is true.
A #controls slot of your own owns the actions
The slot receives status, start and stop — the GrCameraStatus type is exported from
@feugene/granularity-media/components/GrCodeScanner as well. When the slot is passed, only the
status text remains in the frame: the component’s own “Start” button there would be a second one
under the application’s panel.
One code in the frame is one event
The camera gives away dozens of frames per second, and the code is recognised in every one of them. Without a filter the application would receive a stream of identical events and would place twenty orders instead of one, so only what was not in the previous frame is reported.
The symbology is part of the identity: one and the same value in qr_code and in ean_13 counts as
different codes, because it means different things too.
continuous removes the filter: at a receiving desk identical packages are scanned one after
another, and there a repeat is a legitimate second event.
The parsing goes by a timer rather than on every frame
interval (250 ms by default) is a trade between responsiveness and heating: parsing every frame
means keeping the processor busy continuously, and a phone in the hand heats up noticeably.
A frame that did not parse is the norm rather than a failure: a hand, a reflection, a blur got into
the lens. Such a pass is skipped silently, and the next one will come after interval.
Limits
- there is no parsing of a picture from a file. The component works with a live stream; for a shot of a code the application calls its detector directly;
- there is no torch and no zoom.
torchandzoomare extensions of the track, and their support differs even within Chrome; - there is no marking of the found code on the preview. The native API gives away the coordinates, but someone else’s detector does not necessarily, and drawing a frame for half of the sources would mean promising what is not there.
Props in this example