You built the screen, wired the scan button, tested it in the HMI vendor's mobile app, and it reads barcodes. The same button on the same phone in the browser does nothing — camera permission granted, other scripts on the button still running. That split is by design, not a fault: the app exposes the device's native barcode scanner through a platform action, and a browser page has no such action, only a camera stream it must decode itself. This explainer covers why the two clients differ, the three ways that do work in a browser (a decoded preview loop, a still image from a file upload, station hardware that hands the HMI finished text), the pixel math that tells you whether a phone can read your code, and how the scanned value commits into the field.
Why does the scan button work in the app but not the browser?
Because the scan action lives in the mobile app, not in your screen: the app is a native container that bridges the device's own barcode-scanner feature to your button. The browser client has no bridge to that feature, so the call is absent there. Browsers expose no interface for a page to invoke the operating system's scanner. What a browser page can get is a raw camera stream — permission prompt, live video, and decoding left entirely to your code. The app takes the shortcut; the browser has to do the work.
The diagnostic is quick. If a script on the same button still runs in the browser — a log write, a tag change — the button and its events are wired correctly, and only the platform scan call is missing. Granting the browser camera permission changes nothing, because the absent action never asks for the camera. The fix is always one of the three paths below, never a permission.
What camera access does a browser page actually have?
One route: the getUserMedia() call on the MediaDevices API. It prompts the user for consent per site, returns a live video stream you must decode yourself, and runs only when the page loads over https or from localhost. That secure-context requirement is part of the API's own specification, not a browser quirk you can switch off.
The secure-context rule catches many plant setups: a gateway serving screens over plain http on the LAN will not open the camera in the browser at all, whatever the permissions page shows. Put a certificate on the gateway before you blame the code. From there the loop is standard: draw the stream into a video element, sample frames to a canvas, feed them to a barcode decoder, stop at the first result. The webcam barcode scanning in browser-based vision systems guide covers the desktop version of that loop, and Android barcode scanning without a scanner app shows it on a phone outside any scanner app.
Is there any built-in scan action or camera picker in the browser?
No browser offers a page the operating system's scanner, but one built-in camera handoff exists: the file picker of a file-upload input, which on iOS offers the camera in both the browser and the app. On Android, an input limited to images puts the camera app in the chooser. That gives you an upload path with no camera API at all: the operator takes one photo, the page receives it as a file, and your decoder reads the still.
You lose the live retry loop — one shot, then re-upload — but you gain a full-resolution image, and the flow works on locked-down devices where live streams are blocked. Treat the decoded string like any other scan result and validate it before it touches a tag, as the camera barcode decoding: from image to validated data guide lays out.
What are the three workable ways to scan from a browser HMI?
Path A: a getUserMedia preview loop that decodes frames in the page; Path B: a file-upload input that offers the camera and decodes the single still; Path C: station hardware that decodes and pushes the string while the HMI shows the result.
| Path | Runs where | Operator action | Weak point | Best for |
|---|---|---|---|---|
| A. Preview loop | Browser page over https | Aim the phone, auto-decode | Focus hunting, glare, stream resolution | Occasional scans, both platforms |
| B. Photo upload | Any browser, any origin | Take a photo, upload | One shot per attempt, large files | Rare scans, locked-down browsers |
| C. Station hardware | Reader at the machine | Present the part | Wiring, triggering, integration | Every-part reads, line takt |
For Path A, the decode side is the same job as decoding barcodes from a live video stream: pull frames, decode, stop on the first result. Path B needs nothing beyond the upload input and your decoder. Path C moves decoding into fixed code readers or handheld code readers that the HMI treats as a plain data source.
How many pixels per module will the phone preview deliver?
Count on 2 pixels per module as the decode floor and 3 as the working target: at 1920 preview pixels across a 120 mm field of view, a 1 mm module spans 16 pixels, and the same phone fails once the framing widens.
Worked example. A work-order label carries a Code 128 with a 1 mm narrow module, and the operator frames the whole label with a 1920-pixel-wide preview:
- 1920 px ÷ 120 mm = 16 px/mm
- 16 px/mm × 1.0 mm = 16 px per module, eight times the 2-pixel floor
- smallest readable module at that framing: 120 mm × 2 px ÷ 1920 px = 0.125 mm, or 0.19 mm at the 3-pixel target
Now the failure mode: the operator steps back until the frame spans 400 mm. That gives 1920 px ÷ 400 mm = 4.8 px/mm, and 4.8 × 0.33 mm = 1.6 px per module — under the floor, and no retry loop recovers geometry. Request the stream size you intend to decode (phone browsers commonly hand a page 1280 × 720 or 1920 × 1080 regardless of the camera's still rating) and run your numbers through the camera resolution calculator before you build. Module width drives all of it; the barcode module width for document scanning guide covers measuring it off a printed label.
When is the browser the wrong place to read the code?
Move decoding off the phone when parts arrive on a seconds-long takt, when modules fall below about 0.31 mm, when the code sits beyond arm's reach, or when hands are gloved, wet or already full. The 0.31 mm figure is the same arithmetic at a 200 mm field: 200 mm × 3 px ÷ 1920 px = 0.31 mm, the smallest module a phone holds at that framing while keeping the 3-pixel target.
Every-part traceability, verification grades and audit images also argue for station hardware: a reader logs every attempt with its image, on optics that never move. A smart camera with onboard decoding adds inspection in the same unit. Triggering, not decoding, then becomes the integration question — fixed barcode reader trigger modes covers sensor, serial and free-run options.
What hardware reads the code instead of the phone?
A fixed code reader mounts at the station, reads as the part presents, and pushes the decoded string over Ethernet, USB or serial. A handheld scanner keeps the operator's aim and lands as a keyboard or serial device the HMI already understands. Browse the code reader finder by interface and reading distance, or start a station build at the system configurator.
For a code passing one fixed point, a compact reader removes the phone entirely:
- Resolution
- Up to 3 MP
- Shutter
- Global
- Interface
- USB
The LS-CRD-0047 covers a 60-120 mm reading distance over USB 2.0, a presentation-style standoff. When one frame must carry several codes, pick a multi-code rating:
- Interface
- Fast Ethernet
- Resolution
- 5 MP
- Sensor format
- 1/1.7"
- Shutter
- Global
The LS-CRD-0028 decodes up to 60 codes/s at 30 fps through its 8 mm lens, with a 2560 x 2048 sensor behind it. A tethered handheld keeps the human aim but moves the decode onto purpose-built optics:
- Resolution
- Up to 3 MP
- Shutter
- Global
- Interface
- USB
- Connection
- Wired
The LS-HCR-0001 is a 1.0 MP USB 2.0 reader; plug it in and it types. If the handheld carries a laser aimer, treat it as one — aim it at labels, not at faces. A full working pattern with a PLC link is in bottle label barcode verification: fixed reader and PLC serial link.
How does the scanned value get committed to the HMI field?
The same way for every path: the string arrives like typed text, so the target field needs focus, a terminator after the payload, and an Enter commit before the next scan fires. Focus is the part that breaks — a wedge scanner or a decoded preview writes into whatever field holds focus, and a scan fired during a page change silently disappears. The keyboard wedge barcode readers and focus in your HMI guide covers those failure modes, and committing tag data with Enter shows the commit side. On multi-field forms, advancing focus to the next field after each scan keeps operators scanning without tapping.
Which path should you choose for your case?
Commit like this: occasional operator lookups — Path A; locked-down browsers or plain-http hosting you cannot change — Path B; every-part reads on a takt, codes at the phone's pixel floor, or verification and audit images — Path C.
- Operators scan a work order now and then, and both platforms matter: Path A, with the https check done first.
- Scans are rare, devices are locked down, or the gateway stays on plain http: Path B, one photo per scan.
- Every part gets read, the line sets the pace, or the code needs controlled distance and light: Path C — a fixed reader for presented parts, a handheld for operator aim.
Before you build, check four things: the page loads over https (or localhost in test), so Path A can start; the measured module width and worst-case framing have been through the pixels-per-module arithmetic; a no-read behavior is defined — message, retry count, and where the operator goes next; and the target field's focus and commit terminator are set before the first live scan.
Questions
Why didn't granting the browser camera permission fix the scan button?
Browser camera permissions only cover streams a page requests through getUserMedia(). The vendor's scan action never requests one — it exists in the mobile app and is absent from the browser client, so there is nothing for the permission to enable. If other scripts on the same button still run in the browser, the button wiring is fine and the missing piece is the platform scan call itself.
Will getUserMedia open the camera over plain http?
No. getUserMedia() runs only in a secure context, so the page must load over https or from localhost. An HMI served from a gateway over plain http on the plant LAN will not open the camera even with permissions granted — a common second cause after the app-only scan action. A certificate on the gateway, or a localhost tunnel for bench testing, restores it.
Can I decode a barcode from an uploaded photo instead of a live stream?
Yes. Limit the file-upload input to images and the mobile picker offers the camera; on iOS it does so in both the browser and the app. Decode the still with the same decoder you would point at preview frames. A captured still usually carries more pixels than the preview stream, so small modules survive better, but you get one shot per upload with no continuous retry.
How close does the phone have to be for a 0.5 mm module?
Keep the framed width under about 320 mm: 1920 preview pixels × 0.5 mm ÷ 3 pixels per module = 320 mm. At that framing the module spans 3 pixels, the reliable floor; framing half as wide gives 6 pixels and margin for glare and skew. Below 2 pixels per module, decoders stop reporting reads at all.
Is a handheld scanner better than the phone camera for frequent scans?
For scans every few seconds, yes. A tethered handheld lands as a USB keyboard or serial device, so the HMI needs no decode code of its own, and its optics and aimer beat a hand-held phone at arm's length. Keep the phone path for occasional lookups, where fixed hardware for a handful of scans a day is not justified.
What preview resolution should I request from getUserMedia for barcodes?
Request 1280 × 720 as the floor and 1920 × 1080 when the module math needs it; the stream size you request is the resolution you decode, not the camera's still rating. Count 2 pixels per module as the decode floor and 3 as the working target, then run your worst-case framing through the camera resolution calculator before building the loop.


