Webcam capture for photo ID via getUserMedia
Desktop users can now use their webcam to photograph their ID: - Click "Use Camera" → browser requests webcam permission - Live video preview with orange guide rectangle for ID placement - Capture button takes high-res JPEG (1280x720) - Cancel button stops webcam and returns to upload options - Captured image goes through same quality check flow - Works on Chrome, Firefox, Edge, Safari (desktop + mobile) - No libraries needed — native WebRTC API Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c40dfb552e
commit
39fb1c9998
1 changed files with 79 additions and 12 deletions
|
|
@ -238,12 +238,24 @@
|
|||
<svg style="width:20px;height:20px;color:#64748b" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"/></svg>
|
||||
Upload File
|
||||
</button>
|
||||
<button type="button" id="dot-id-scan-btn" style="display:flex;align-items:center;gap:8px;padding:10px 20px;background:#fff;border:1px solid #d1d5db;border-radius:8px;cursor:pointer;color:#374151;font-size:13px;font-weight:500">
|
||||
<button type="button" id="dot-id-cam-btn" style="display:flex;align-items:center;gap:8px;padding:10px 20px;background:#fff;border:1px solid #d1d5db;border-radius:8px;cursor:pointer;color:#374151;font-size:13px;font-weight:500">
|
||||
<svg style="width:20px;height:20px;color:#64748b" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M6.827 6.175A2.31 2.31 0 015.186 7.23c-.38.054-.757.112-1.134.175C2.999 7.58 2.25 8.507 2.25 9.574V18a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9.574c0-1.067-.75-1.994-1.802-2.169a47.865 47.865 0 00-1.134-.175 2.31 2.31 0 01-1.64-1.055l-.822-1.316a2.192 2.192 0 00-1.736-1.039 48.774 48.774 0 00-5.232 0 2.192 2.192 0 00-1.736 1.039l-.821 1.316z"/><path stroke-linecap="round" stroke-linejoin="round" d="M16.5 12.75a4.5 4.5 0 11-9 0 4.5 4.5 0 019 0zM18.75 10.5h.008v.008h-.008V10.5z"/></svg>
|
||||
Camera / Scanner
|
||||
Use Camera
|
||||
</button>
|
||||
</div>
|
||||
<input type="file" id="dot-photo-id-scan" accept="image/*" capture="environment" style="display:none" />
|
||||
<!-- Webcam capture area (hidden until activated) -->
|
||||
<div id="dot-id-webcam" hidden style="margin-top:12px">
|
||||
<div style="position:relative;max-width:400px;margin:0 auto;border-radius:8px;overflow:hidden;border:2px solid #1a2744">
|
||||
<video id="dot-id-video" autoplay playsinline style="width:100%;display:block;background:#000"></video>
|
||||
<div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);pointer-events:none;border:2px dashed rgba(249,115,22,0.6);border-radius:8px;width:85%;height:60%"></div>
|
||||
</div>
|
||||
<p style="font-size:11px;color:#64748b;text-align:center;margin:6px 0">Position your ID within the orange guide</p>
|
||||
<div style="display:flex;gap:8px;justify-content:center;margin-top:8px">
|
||||
<button type="button" id="dot-id-capture" style="padding:10px 32px;background:#f97316;color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer">📷 Capture</button>
|
||||
<button type="button" id="dot-id-cam-cancel" style="padding:10px 20px;background:#fff;color:#374151;border:1px solid #d1d5db;border-radius:8px;font-size:13px;cursor:pointer">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
<canvas id="dot-id-canvas" style="display:none"></canvas>
|
||||
<div style="text-align:center;margin:12px 0 0">
|
||||
<div style="display:flex;align-items:center;gap:12px;justify-content:center;margin-bottom:8px">
|
||||
<div style="flex:1;height:1px;background:#d1d5db"></div>
|
||||
|
|
@ -451,10 +463,66 @@
|
|||
// Upload button — opens file picker
|
||||
idBtn?.addEventListener("click", function() { if (idInput) idInput.click(); });
|
||||
|
||||
// Scanner/camera button
|
||||
var idScanBtn = document.getElementById("dot-id-scan-btn");
|
||||
var idScanInput = document.getElementById("dot-photo-id-scan");
|
||||
idScanBtn?.addEventListener("click", function() { if (idScanInput) idScanInput.click(); });
|
||||
// Webcam camera button
|
||||
var idCamBtn = document.getElementById("dot-id-cam-btn");
|
||||
var idWebcam = document.getElementById("dot-id-webcam");
|
||||
var idVideo = document.getElementById("dot-id-video");
|
||||
var idCaptureBtn = document.getElementById("dot-id-capture");
|
||||
var idCamCancel = document.getElementById("dot-id-cam-cancel");
|
||||
var idCanvas = document.getElementById("dot-id-canvas");
|
||||
var webcamStream = null;
|
||||
|
||||
idCamBtn?.addEventListener("click", function() {
|
||||
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
||||
alert("Camera not available in this browser. Please use Upload File instead.");
|
||||
return;
|
||||
}
|
||||
navigator.mediaDevices.getUserMedia({ video: { width: { ideal: 1280 }, height: { ideal: 720 }, facingMode: "environment" } })
|
||||
.then(function(stream) {
|
||||
webcamStream = stream;
|
||||
idVideo.srcObject = stream;
|
||||
if (idWebcam) idWebcam.hidden = false;
|
||||
if (idUploadOpts) {
|
||||
// Hide upload options but keep webcam visible
|
||||
var buttons = idUploadOpts.querySelector("div");
|
||||
if (buttons) buttons.style.display = "none";
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
alert("Could not access camera: " + err.message + ". Please use Upload File instead.");
|
||||
});
|
||||
});
|
||||
|
||||
idCaptureBtn?.addEventListener("click", function() {
|
||||
if (!idVideo || !idCanvas) return;
|
||||
idCanvas.width = idVideo.videoWidth;
|
||||
idCanvas.height = idVideo.videoHeight;
|
||||
var ctx = idCanvas.getContext("2d");
|
||||
ctx.drawImage(idVideo, 0, 0);
|
||||
// Stop webcam
|
||||
if (webcamStream) {
|
||||
webcamStream.getTracks().forEach(function(t) { t.stop(); });
|
||||
webcamStream = null;
|
||||
}
|
||||
if (idWebcam) idWebcam.hidden = true;
|
||||
// Convert to blob and handle as file
|
||||
idCanvas.toBlob(function(blob) {
|
||||
var file = new File([blob], "photo-id-capture.jpg", { type: "image/jpeg" });
|
||||
handleIdFile(file);
|
||||
}, "image/jpeg", 0.92);
|
||||
});
|
||||
|
||||
idCamCancel?.addEventListener("click", function() {
|
||||
if (webcamStream) {
|
||||
webcamStream.getTracks().forEach(function(t) { t.stop(); });
|
||||
webcamStream = null;
|
||||
}
|
||||
if (idWebcam) idWebcam.hidden = true;
|
||||
if (idUploadOpts) {
|
||||
var buttons = idUploadOpts.querySelector("div");
|
||||
if (buttons) buttons.style.display = "";
|
||||
}
|
||||
});
|
||||
|
||||
// QR code button — show QR with current page URL for phone upload
|
||||
idQrBtn?.addEventListener("click", function() {
|
||||
|
|
@ -597,7 +665,9 @@
|
|||
window.__dotPhotoId = null;
|
||||
idAccepted = false;
|
||||
if (idInput) idInput.value = "";
|
||||
if (idScanInput) idScanInput.value = "";
|
||||
// Stop webcam if running
|
||||
if (webcamStream) { webcamStream.getTracks().forEach(function(t) { t.stop(); }); webcamStream = null; }
|
||||
if (idWebcam) idWebcam.hidden = true;
|
||||
if (idPreview) idPreview.hidden = true;
|
||||
if (idUploadOpts) idUploadOpts.style.display = "";
|
||||
// Reset quality check UI
|
||||
|
|
@ -612,10 +682,7 @@
|
|||
handleIdFile(idInput.files?.[0]);
|
||||
});
|
||||
|
||||
// Scanner input handler
|
||||
idScanInput?.addEventListener("change", function() {
|
||||
handleIdFile(idScanInput.files?.[0]);
|
||||
});
|
||||
// (webcam capture handled above via getUserMedia)
|
||||
|
||||
} // end guard
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue