GrCodeScanner

  • media
  • media
  • API · 10 props
import { GrCodeScanner } from '@feugene/granularity-media/components/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 continuous is 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

NeedTake
Take a photo rather than read a codeGrCameraCapture
Choose a crop out of a ready pictureGrImageCrop
Accept a file with a shot of a codeGrFileUpload

Examples 3 each with a live demo and its code

  • Receiving cartons against an order

    continuous keeps 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. formats narrows the search to EAN-13 and Code 128, interval sets 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 progress

    The camera is off.

    Desk lamp, black400638133393124 / 24
    Desk lamp, white400638133394817 / 24
    Monitor arm, single59012341234570 / 12
    Cable tray, 80 cm761234567890440 / 40
    Scan cartons in any order; repeats count.
  • Check-in at the door, with its own panel

    The controls slot 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

PropTypedefaultDescription
facing"user" | "environment" | undefined"environment"Which camera: `environment` — the rear one, the one that scans.
deviceIdstring | undefinedundefinedA specific device, if the application has already picked one.
autoStartboolean | undefinedfalseTurn 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.
sizeGrCodeScannerSize | undefinedundefinedThe size step: the controls and the type size of the captions. `md` by default; taken from `GrConfigProvider`.
disabledboolean | undefinedfalseThe scanner is unavailable: the camera does not turn on, the buttons are disabled.
ariaLabelstring | undefinedundefinedThe name of the group with the preview and the buttons. Not set — from the locale ("Code scanner").
formatsreadonly string[] | undefinedundefinedSymbologies: `qr_code`, `ean_13`, `code_128`, … Without a list — everything the browser can do.
detectorGrCodeDetector | undefinedundefinedA 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.
intervalnumber | undefined250How often to parse a frame, ms. More often — a hot device, less often — a sluggish response.
continuousboolean | undefinedfalseReport the same code again: goods receiving scans one after another.

Slots

SlotTypeDescription
controls{ status: GrCameraStatus; start: () => void; stop: () => void; }Your own control panel instead of the built-in one.

Events

EventSignatureDescription
start(event: "start"): void—
stop(event: "stop"): void—
statusChange(event: "statusChange", status: GrCameraStatus): void—
detect(event: "detect", codes: GrCodeResult[]): void—

Methods

MethodsSignatureDescription
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() => voidStop parsing frames and turn the camera off; the state returns to `idle`.
statusGrCameraStatusThe 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
import type { GrCameraStatus, GrCodeResult, GrCodeDetector } from '@feugene/granularity-media/components/GrCodeScanner'

Filedist/types/components/shared/cameraState.d.ts

GrCameraStatustype
type GrCameraStatus = 'idle'

Filedist/types/components/GrCodeScanner/codeDetection.d.ts

GrCodeResultinterface
interface GrCodeResult {
    value: string;
    format: string;
}
GrCodeDetectortype
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[];
nativeDetectorSupportedfunction
function nativeDetectorSupported(scope?: typeof globalThis): boolean;
createNativeDetectorfunction
function createNativeDetector(formats: readonly string[] | undefined, scope?: typeof globalThis): GrCodeDetector | null;
freshCodesfunction
function freshCodes(previous: readonly GrCodeResult[], next: readonly GrCodeResult[], continuous: boolean): GrCodeResult[];

Filedist/types/components/GrCodeScanner/config.d.ts

grCodeScannerConfigconst
const grCodeScannerConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrCodeScanner">;

Filedist/types/components/GrCodeScanner/defaults.d.ts

GrCodeScannerConfigurablePropsinterface
interface GrCodeScannerConfigurableProps {
    size?: GrCodeScannerSize;
}

Filedist/types/components/GrCodeScanner/GrCodeScanner.vue.d.ts

GrCodeScannerPropsinterface
interface GrCodeScannerProps {
    facing?: 'user' | 'environment';
    deviceId?: string;
    autoStart?: boolean;
    formats?: readonly string[];
    detector?: GrCodeDetector;
    interval?: number;
    continuous?: boolean;
    size?: GrCodeScannerSize;
    disabled?: boolean;
    ariaLabel?: string;
}
GrCodeScannerEmitsinterface
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

GrCodeScannerSizetype
type GrCodeScannerSize = 'xs' | 'sm' | 'md' | 'lg';
grCodeScannerSafelistconst
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:

ts
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()) }] : []
}
vue
<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. torch and zoom are 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.