如何在javascript中通过手机上的网络浏览器使用实时流扫描条形码?
Posted
技术标签:
【中文标题】如何在javascript中通过手机上的网络浏览器使用实时流扫描条形码?【英文标题】:How to scan a barcode with livestream through webbrowser on on mobile phone in javascript? 【发布时间】:2018-08-30 16:49:08 【问题描述】:我正在尝试一种方法来扫描我打印在纸上的 irl 条形码以扫描到我的 php 网站。只有当我在手机上使用该网站时,我才能打开手机摄像头并扫描条形码,而他则扫描条形码并将其发送到我的代码。该代码可以检查我的数据库等。
我找到了一个漂亮的插件,叫做QuaggaJS,我一直在玩它,现在我比拍照更进一步,让它读取条形码并将其发送到我的代码,但我想要当我把相机放在条形码前面时让它扫描,这样它就会把相机关在自己身上。这是example(在桌面上它会请求打开网络摄像头的权限)。
最后,我想要一个按钮,在我的网站上单击它可以打开我的相机,这样我就可以扫描代码,当他找到条形码时,他会关闭相机并向我显示具有此条形码的产品的信息。有人可以帮助我吗?
这是我使用和玩过的一些代码。 (仅适用于拍照)
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<script type="text/javascript" src="includes/js/quagga.min.js"></script>
<link href="https://use.fontawesome.com/releases/v5.0.8/css/all.css" rel="stylesheet">
<script type="text/javascript" src="includes/js/quagga.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
<style>
#interactive.viewport position: relative; width: 100%; height: auto; overflow: hidden; text-align: center;
#interactive.viewport > canvas, #interactive.viewport > video max-width: 100%;width: 100%;
canvas.drawing, canvas.drawingBuffer position: absolute; left: 0; top: 0;
</style>
<script type="text/javascript">
$(function()
// Create the QuaggaJS config object for the live stream
var liveStreamConfig =
inputStream:
type : "LiveStream",
constraints:
width: min: 640,
height: min: 480,
aspectRatio: min: 1, max: 100,
facingMode: "environment" // or "user" for the front camera
,
locator:
patchSize: "medium",
halfSample: true
,
numOfWorkers: (navigator.hardwareConcurrency ? navigator.hardwareConcurrency : 4),
decoder:
"readers":[
"format":"code_128_reader","config":
]
,
locate: true
;
// The fallback to the file API requires a different inputStream option.
// The rest is the same
var fileConfig = $.extend(
,
liveStreamConfig,
inputStream:
size: 800
);
// Start the live stream scanner when the modal opens
$('#livestream_scanner').on('shown.bs.modal', function (e)
Quagga.init(
liveStreamConfig,
function(err)
if (err)
$('#livestream_scanner .modal-body .error').html('<div class="alert alert-danger"><strong><i class="fa fa-exclamation-triangle"></i> '+err.name+'</strong>: '+err.message+'</div>');
Quagga.stop();
return;
Quagga.start();
);
);
// Make sure, QuaggaJS draws frames an lines around possible
// barcodes on the live stream
Quagga.onProcessed(function(result)
var drawingCtx = Quagga.canvas.ctx.overlay,
drawingCanvas = Quagga.canvas.dom.overlay;
if (result)
if (result.boxes)
drawingCtx.clearRect(0, 0, parseInt(drawingCanvas.getAttribute("width")), parseInt(drawingCanvas.getAttribute("height")));
result.boxes.filter(function (box)
return box !== result.box;
).forEach(function (box)
Quagga.ImageDebug.drawPath(box, x: 0, y: 1, drawingCtx, color: "green", lineWidth: 2);
);
if (result.box)
Quagga.ImageDebug.drawPath(result.box, x: 0, y: 1, drawingCtx, color: "#00F", lineWidth: 2);
if (result.codeResult && result.codeResult.code)
Quagga.ImageDebug.drawPath(result.line, x: 'x', y: 'y', drawingCtx, color: 'red', lineWidth: 3);
);
// Once a barcode had been read successfully, stop quagga and
// close the modal after a second to let the user notice where
// the barcode had actually been found.
Quagga.onDetected(function(result)
if (result.codeResult.code)
$('#scanner_input').val(result.codeResult.code);
Quagga.stop();
setTimeout(function() $('#livestream_scanner').modal('hide'); , 1000);
);
// Stop quagga in any case, when the modal is closed
$('#livestream_scanner').on('hide.bs.modal', function()
if (Quagga)
Quagga.stop();
);
// Call Quagga.decodeSingle() for every file selected in the
// file input
$("#livestream_scanner input:file").on("change", function(e)
if (e.target.files && e.target.files.length)
Quagga.decodeSingle($.extend(, fileConfig, src: URL.createObjectURL(e.target.files[0])), function(result) alert(result.codeResult.code););
);
);
</script>
</head>
<body>
<input id="scanner_input" class="form-control" style="width:20%;" placeholder="Barcode" type="text" />
<button class="btn btn-default" type="button" data-toggle="modal" data-target="#livestream_scanner"><i class="fas fa-barcode"></i> Scan</button>
<div class="modal" id="livestream_scanner">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<h4 class="modal-title">Barcode Scanner</h4>
</div>
<div class="modal-footer">
<label class="btn btn-default pull-left">
<i class="fa fa-camera"></i> Use camera app
<input type="file" accept="image/*;capture=camera" capture="camera" class="hidden" />
</label>
<button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
</body>
</html>
最好的问候
【问题讨论】:
【参考方案1】:我花了一些时间自己完成它,但我发现了。我还必须获得证书,这样我的网站才能安全并允许我使用 GetUserMedia();
这是我现在使用的代码。使用$code = $_GET['barcode'];
,我可以使用该变量来做其他事情,这完全取决于您自己。
php代码
<button id="opener">Barcode scanner</button>
<div id="modal" title="Barcode scanner">
<span class="found"></span>
<div id="interactive" class="viewport"></div>
</div>
Javascript/QuaggaJS
$(function()
var App =
init : function()
Quagga.init(this.state, function(err)
if (err)
console.log(err);
return;
App.attachListeners();
App.checkCapabilities();
Quagga.start();
);
,
checkCapabilities: function()
var track = Quagga.CameraAccess.getActiveTrack();
var capabilities = ;
if (typeof track.getCapabilities === 'function')
capabilities = track.getCapabilities();
this.applySettingsVisibility('zoom', capabilities.zoom);
this.applySettingsVisibility('torch', capabilities.torch);
,
updateOptionsForMediaRange: function(node, range)
console.log('updateOptionsForMediaRange', node, range);
var NUM_STEPS = 6;
var stepSize = (range.max - range.min) / NUM_STEPS;
var option;
var value;
while (node.firstChild)
node.removeChild(node.firstChild);
for (var i = 0; i <= NUM_STEPS; i++)
value = range.min + (stepSize * i);
option = document.createElement('option');
option.value = value;
option.innerHTML = value;
node.appendChild(option);
,
applySettingsVisibility: function(setting, capability)
if (typeof capability === 'boolean')
var node = document.querySelector('input[name="settings_' + setting + '"]');
if (node)
node.parentNode.style.display = capability ? 'block' : 'none';
return;
if (window.MediaSettingsRange && capability instanceof window.MediaSettingsRange)
var node = document.querySelector('select[name="settings_' + setting + '"]');
if (node)
this.updateOptionsForMediaRange(node, capability);
node.parentNode.style.display = 'block';
return;
,
initCameraSelection: function()
var streamLabel = Quagga.CameraAccess.getActiveStreamLabel();
return Quagga.CameraAccess.enumerateVideoDevices()
.then(function(devices)
function pruneText(text)
return text.length > 30 ? text.substr(0, 30) : text;
var $deviceSelection = document.getElementById("deviceSelection");
while ($deviceSelection.firstChild)
$deviceSelection.removeChild($deviceSelection.firstChild);
devices.forEach(function(device)
var $option = document.createElement("option");
$option.value = device.deviceId || device.id;
$option.appendChild(document.createTextNode(pruneText(device.label || device.deviceId || device.id)));
$option.selected = streamLabel === device.label;
$deviceSelection.appendChild($option);
);
);
,
attachListeners: function()
var self = this;
self.initCameraSelection();
$(".controls").on("click", "button.stop", function(e)
e.preventDefault();
Quagga.stop();
);
$(".controls .reader-config-group").on("change", "input, select", function(e)
e.preventDefault();
var $target = $(e.target),
value = $target.attr("type") === "checkbox" ? $target.prop("checked") : $target.val(),
name = $target.attr("name"),
state = self._convertNameToState(name);
console.log("Value of "+ state + " changed to " + value);
self.setState(state, value);
);
,
_accessByPath: function(obj, path, val)
var parts = path.split('.'),
depth = parts.length,
setter = (typeof val !== "undefined") ? true : false;
return parts.reduce(function(o, key, i)
if (setter && (i + 1) === depth)
if (typeof o[key] === "object" && typeof val === "object")
Object.assign(o[key], val);
else
o[key] = val;
return key in o ? o[key] : ;
, obj);
,
_convertNameToState: function(name)
return name.replace("_", ".").split("-").reduce(function(result, value)
return result + value.charAt(0).toUpperCase() + value.substring(1);
);
,
detachListeners: function()
$(".controls").off("click", "button.stop");
$(".controls .reader-config-group").off("change", "input, select");
,
applySetting: function(setting, value)
var track = Quagga.CameraAccess.getActiveTrack();
if (track && typeof track.getCapabilities === 'function')
switch (setting)
case 'zoom':
return track.applyConstraints(advanced: [zoom: parseFloat(value)]);
case 'torch':
return track.applyConstraints(advanced: [torch: !!value]);
,
setState: function(path, value)
var self = this;
if (typeof self._accessByPath(self.inputMapper, path) === "function")
value = self._accessByPath(self.inputMapper, path)(value);
if (path.startsWith('settings.'))
var setting = path.substring(9);
return self.applySetting(setting, value);
self._accessByPath(self.state, path, value);
console.log(JSON.stringify(self.state));
App.detachListeners();
Quagga.stop();
App.init();
,
inputMapper:
inputStream:
constraints: function(value)
if (/^(\d+)x(\d+)$/.test(value))
var values = value.split('x');
return
width: min: parseInt(values[0]),
height: min: parseInt(values[1])
;
return
deviceId: value
;
,
numOfWorkers: function(value)
return parseInt(value);
,
decoder:
readers: function(value)
if (value === 'ean_extended')
return [
format: "ean_reader",
config:
supplements: [
'ean_5_reader', 'ean_2_reader'
]
];
return [
format: value + "_reader",
config:
];
,
state:
inputStream:
type : "LiveStream",
constraints:
width: min: 640,
height: min: 480,
aspectRatio: min: 1, max: 100,
facingMode: "environment" // or user
,
locator:
patchSize: "medium",
halfSample: true
,
numOfWorkers: 2,
frequency: 10,
decoder:
readers : [
format: "code_128_reader",
config:
]
,
locate: true
,
lastResult : null
;
App.init();
Quagga.onDetected(function(result)
var code = result.codeResult.code;
Quagga.stop();
window.location.href="scannerview.php?barcode=" + code;
);
);
【讨论】:
FWIW,aspectRatio 的“max: 100”参数绝对会在 ios 14 中搞砸。 当时没有 iOS 14 我知道,我正试图消除它的任何用途,因为我现在正在维护代码,并试图让很多人突然遇到问题。 max: 2 效果更好。 我正在寻找类似的东西。关于证书的一个问题。你说的是https证书吗?如果你需要 https 证书,你如何在本地测试它导致本地主机上没有 https 对吗?谢谢 你确实需要 https 证书,我在实习期间正在研究这个,所以他们把这些事情整理好了。以上是关于如何在javascript中通过手机上的网络浏览器使用实时流扫描条形码?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Javascript 在浏览器中通过 X-Y 坐标定位?
在 JavaScript 中通过 JSON 对象进行类似 Lucene 的搜索
在 xul 和 javascript 中通过按钮单击事件上的另一个工具提示文本更改工具提示文本