自定义webIpad证件相机(webRTC)
创始人
2024-12-17 11:36:15
0
该技术方案可用于各浏览器自定义相机开发

相机UI(index.html)

                  自定义相机                       

Failed to obtain the rear camera of the device. Please try another solution to obtain resources!

Loading...

 相机UI样式(style.css)

* {     margin: 0;     padding: 0;     box-sizing: border-box;     border: 0; }  html, body {     width: 100%;     height: 100%;     overflow: hidden;     background-color: #000;     color: #fff; }  .cancleBtn {     padding: 2vw 0;     width: 100%; }  .takeOffTip {     position: fixed;     padding-top: 2vw;     top: 0;     left: 0;     width: 100%;     font-size: 1.8vw;     text-align: center;     color: #fff; }  .bottonSize {     height: 100%;     line-height: 6vw;     line-height: 6dvw;     padding: 0 1.5vw; }  .bottomBtnBox, .rightBtnBox {     position: fixed;     right: 0;     display: flex;     justify-content: space-between;     align-items: center;     background-color: #000;     z-index: 10; }  .bottomBtnBox {     bottom: 0;     width: 100%;     height: 6vw;     height: 6dvw; }  .rightBtnBox {     flex-direction: column;     top: 0;     height: 100%;     width: 6vw;     width: 6dvw; }  html[prew='-1'] .bottomBtnBox, html[prew='0'] .bottomBtnBox, html[prew='-1'] .rightBtnBox, html[prew='1'] .rightBtnBox, html[prew='1'] .customer_carema {     display: none; }  html[prew='1'] .imgBox {     border: 0;     font-size: 0;     opacity: 0; }  .takeBtn {     padding: 4px;     width: 5vw;     width: 5dvw;     height: 5vw;     height: 5dvw;     background-color: #fff;     border-radius: 50%; }  .takeBtn::before {     content: '';     display: block;     width: 100%;     height: 100%;     border: 5px solid #000;     background-color: #fff;     border-radius: 50%;     box-sizing: border-box; }  .rightBtnBox::before {     content: '';     display: block; }  .btn {     background-color: #000;     text-align: center;     font-size: 1.5vw;     color: #fff; }  .customer_video, .carema_img, .cuteImg {     width: 100%;     height: 100%;     object-fit: cover; }  .imgBoxDom {     position: fixed;     top: 0;     left: 0;     width: 100%;     height: 100%;     display: flex;     justify-content: center;     align-items: center;     z-index: 9; }  .imgBox {     width: var(--carema-box-width);     height: var(--carema-box-height);     border: 2px solid #fff;     display: flex;     justify-content: center;     align-items: center;     font-size: 10vw;     z-index: 10;     border-radius: 2vw; }  .errTip {     position: fixed;     top: 0;     left: 0;     width: 100%;     height: 100%;     z-index: 8888;     display: none;     flex-direction: column;     justify-content: center;     align-items: center;     background-color: #000; }  .errTip>p {     padding-bottom: 20px;     color: #fff; }  .errTip button {     padding: 10px 30px; }  html[prew='2'] .errTip {     display: flex; }  html[loaded='1'] .loading-css {     display: none; }  .loading-css {     position: fixed;     top: 0;     left: 0;     width: 100%;     height: 100%;     display: flex;     flex-direction: column;     justify-content: center;     align-items: center;     background-color: #000;     z-index: 9999; }  .loading-css::before {     margin-bottom: 10px;     content: '';     width: 50px;     height: 50px;     display: inline-block;     border: 3px solid #f3f3f3;     border-top: 3px solid rgb(160, 155, 155);     border-radius: 50%;     animation: loading-360 0.8s infinite linear; }  @keyframes loading-360 {     0% {         transform: rotate(0deg);     }      100% {         transform: rotate(360deg);     } }

调试UI(carema.html)
 

                  调试相机             

相机逻辑基础(index.js)

function WbCRM() {     this.body = document.body;     this.html = document.documentElement;     this.takeBtn = document.querySelector('.takeBtn');     this.imgBox = document.querySelector('.imgBox');     this.reTakeBtn = document.querySelector('.reTakeBtn');     this.cancleBtn = document.querySelector('.cancleBtn');     this.nextBtn = document.querySelector('.nextBtn');     var errBtn = document.querySelector('.errBtn');     this.video = null;     this.err = null;     this.fullImg = null;     this.file = '';     this.idType = '';     this.isDev = false;      this.stream = null;     this.openId = '';      this.ratio = window.devicePixelRatio || 1;     this.videoWidth = this.body.clientWidth * this.ratio;     this.videoHeight = this.body.clientHeight * this.ratio;      this.html.setAttribute('prew', '-1');     var isMp3 = !(navigator.userAgent.match(/Firefox/));     var audio = new Audio();     audio.autoplay = isMp3 ? './shutter.mp3' : './shutter.ogg';     this.audio = audio;     console.log(isMp3,audio);      this.mediaDevices = (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) ?         navigator.mediaDevices : ((navigator.mozGetUserMedia || navigator.webkitGetUserMedia) ? {             getUserMedia: function (c) {                 return new Promise(function (y, n) {                     (navigator.mozGetUserMedia ||                         navigator.webkitGetUserMedia).call(navigator, c, y, n);                 });             }         } : null);     this.setDom();     this.setCarema();     this.takeBtn.addEventListener('click', this.takePhoto.bind(this));     this.nextBtn.addEventListener('click', this.next.bind(this));     this.reTakeBtn.addEventListener('click', this.reTake.bind(this));     this.cancleBtn.addEventListener('click', this.cancle.bind(this));     errBtn.addEventListener('click', this.openErro.bind(this)); } WbCRM.prototype.openErro = function () {     this.sendMsg('open_erro'); } WbCRM.prototype.cancle = function () {     this.removeStream();     this.sendMsg('off_carema'); } WbCRM.prototype.next = function () {     if (this.fullImg) this.fullImg.remove();     this.removeStream();     this.sendMsg('success_file'); } WbCRM.prototype.reTake = function () {     this.file = null;     this.err = null;     if (this.fullImg) this.fullImg.remove();     this.html.setAttribute('loaded', 0);     this.removeStream();     this.setCarema(); } WbCRM.prototype.cutImage = function () {     var boxWidth = this.imgBox.clientWidth * this.ratio;     var boxHeight = this.imgBox.clientHeight * this.ratio;     var vLeft = (this.videoWidth - boxWidth) / 2;     var vTop = (this.videoHeight - boxHeight) / 2;     var nCanvas = wbCRMTools.drawHighDefinitionImg(boxWidth, boxHeight);     var nCtx = nCanvas.getContext('2d');     nCtx.drawImage(this.fullImg, -vLeft, -vTop);     var cutImage = nCtx.getImageData(0, 0, boxWidth, boxHeight);     wbCRMTools.changeImgData(cutImage?.data || [], this.idType || '');     nCtx.putImageData(cutImage, 0, 0);     reImgUrl = nCanvas.toDataURL('image/jpeg');     var cImg = document.createElement('img');     cImg.src = reImgUrl;     this.file = wbCRMTools.canvas2File(reImgUrl);     wbCRMTools.clearCanvas(nCtx, nCanvas);     cImg.className = "cuteImg";     this.imgBox.append(cImg);     this.html.setAttribute('prew', '1');     this.removeStream(); } WbCRM.prototype.takePhoto = function () {     var gCanvas = wbCRMTools.drawHighDefinitionImg(this.videoWidth, this.videoHeight);     var originalCtx = gCanvas.getContext('2d');     originalCtx.drawImage(this.video, 0, 0, this.videoWidth, this.videoHeight);      var imgUrl = gCanvas.toDataURL('image/jpeg');     var fullImg = document.createElement("img");     fullImg.className = "carema_img";     fullImg.src = imgUrl;     this.fullImg = fullImg;     this.body.append(fullImg);     wbCRMTools.clearCanvas(originalCtx, gCanvas);     this.audio.play();     fullImg.onload = this.cutImage.bind(this); }  WbCRM.prototype.sendMsg = function (mothod) {     this.audio.remove();     const origin = this.isDev ? undefined : window.location.origin;     window.opener.postMessage({ mothod: mothod, file: this.file, openId: this.openId, error: this.err }, origin);     window.close(); }  WbCRM.prototype.removeStream = function () {     var self = this;     if (self.stream) {         self.stream.getTracks().forEach(function (track) {             if (track.readyState === 'live') track.stop();             self.stream.removeTrack(track);         });     }     if (this.video) this.video.remove();     var cuteImgList = document.querySelectorAll('.cuteImg');     cuteImgList.forEach(function (dom) {         dom.remove();     }) }  WbCRM.prototype.setDom = function () {     this.openId = wbCRMTools.getUrlParam('openId');     var okText = wbCRMTools.getUrlParam('continue');     var cancelText = wbCRMTools.getUrlParam('cancel');     var retakeText = wbCRMTools.getUrlParam('retake');     var idType = wbCRMTools.getUrlParam('idType') || '';     var takeOffTip = wbCRMTools.getUrlParam('takeOffTip');     const isDev = wbCRMTools.getUrlParam('isDev');     this.isDev = isDev === '1';     this.nextBtn.innerText = okText || 'Cuntinue';     this.cancleBtn.innerText = cancelText || 'Cancel';     this.reTakeBtn.innerText = retakeText || 'Retake';     document.querySelector('.takeOffTip').innerHTML = takeOffTip;     this.html.setAttribute('loaded', 0);     this.html.style.setProperty('--carema-box-width', '64.512vw');     this.html.style.setProperty('--carema-box-height', '40.6789vw');     if (idType === "LANDING") {         this.html.style.setProperty('--carema-box-width', '51.2vw');         this.html.style.setProperty('--carema-box-height', '44.5935vw');     }     this.idType = idType; }  WbCRM.prototype.setVideo = function (stream) {     var video = document.createElement('video');     video.setAttribute('autoplay', 'autoplay');     video.setAttribute('playsinline', 'playsinline');     video.className = 'customer_video';     this.video = video;     this.stream = stream;     this.body.append(video);     var self = this;     video.onloadedmetadata = function (e) {         self.stream = stream;         self.loaded = true;         self.html.setAttribute('loaded', 1);     };     video.onplay = function () {         self.html.setAttribute('prew', '0');     }     // as window.URL.createObjectURL() is deprecated, adding a check so that it works in Safari.     // older browsers may not have srcObject     if ("srcObject" in video) {         video.srcObject = stream;     } else {         // using URL.createObjectURL() as fallback for old browsers         video.src = window.URL.createObjectURL(stream);     } }  WbCRM.prototype.setCarema = function () {     const videoConf = this.isDev ? {} : {         width: { min: 1024, ideal: 2360, max: 2732 },         height: { min: 776, ideal: 1640, max: 2048 },         facingMode: { exact: "environment" }     }     var self = this;     this.mediaDevices.getUserMedia({         audio: false,         video: videoConf     }).then(this.setVideo.bind(this)).catch(function (error) {         self.err = error.toString();         self.html.setAttribute('prew', '2');         self.html.setAttribute('loaded', '1');     }) }  window.addEventListener('load', function () {     var wbCRM = new WbCRM();     window.addEventListener('visibilitychange', function () {         wbCRM.removeStream();         window.close();     }); });  

图片出路和文件生成工具(tools.js) 

var wbCRMTools = {     drawHighDefinitionImg: function (width, height) {         const canvas = document.createElement('canvas');         canvas.style.width = width + 'px';         canvas.style.height = height + 'px';         canvas.width = width;         canvas.height = height;         return canvas;     },     clearCanvas: function (ctx, canvas) {         ctx.clearRect(0, 0, canvas.width, canvas.height);         ctx.beginPath();         canvas.height = 0;         canvas.width = 0;         canvas.remove();         canvas.parentNode?.removeChild(canvas);     },      changeImgData: function (data, idType) {         const isGrayscale = ['PASSPORT', 'LANDING', 'ENTRYPERMIT', 'SUP_LEGAL_ID'].some(imgType => idType.indexOf(imgType) !== -1);         let contrast = 35;         const thereshold = 20;         if ('LANDING' === idType) contrast = 45;         // gaussBlur will use in the feature, cancel this fun now, don`t delete please         // this.gaussBlur(imageData, 1);         // If MacId and HK-LANDING change cavans-img-code.         const factor = (255 + contrast) / (255.01 - contrast);  //add .1 to avoid /0 error         const denominator = 1 / (1 - contrast / 255) - 1;         const setCV = cv => cv + (cv - thereshold) * denominator;         const setCTV = cv => cv + (cv - thereshold) * contrast / 255;         const getRGB = cv => factor * (cv - 128) + 128;         // Data array data-length.         const len = data?.length || 0;         // loop value to change cavans imgData;         for (let index = 0; index < len; index += 4) {             let R = data[index];     //r value             let G = data[index + 1]; //g value             let B = data[index + 2] //b value             if (contrast || thereshold) {                 R = getRGB(R); //r value                 G = getRGB(G); //g value                 B = getRGB(B); //b value             }             const isColorNum = index % 4 === 0;             if (isColorNum) {                 R = contrast ? setCV(R) : setCTV(R);                 G = contrast ? setCV(G) : setCTV(G);                 B = contrast ? setCV(B) : setCTV(B);                 if (isGrayscale) {                     const vNum = Math.round((R + G + B) / 3);                     R = vNum;                     G = vNum;                     B = vNum;                     data[index + 3] = 255;                 }                 data[index] = R;                 data[index + 1] = G;                 data[index + 2] = B;             }         }     },     getUrlParam: function (urlKey) {         var url = window.location.search;         var reg = new RegExp("(^|&)" + urlKey + "=([^&]*)(&|$)");         var result = url.substring(1).match(reg);         return result ? decodeURIComponent(result[2]) : null;     },     canvas2File: function (dataUrl) {         let arr = dataUrl.split(','),             mime = arr[0].match(/:(.*?);/)[1],             bstr = atob(arr[1]),             n = bstr.length,             u8arr = new Uint8Array(n);         while (n--) {             u8arr[n] = bstr.charCodeAt(n);         }         const nowId = Date.now();         const fileName = `takePhoto_${nowId}.jpeg`;         const blob = new Blob([u8arr], { type: mime, name: fileName });         blob.lastModifiedDate = new Date();         return new File([blob], fileName, { type: "image/jpeg" });     } } 

 文件目录

效果图

相关内容

热门资讯

透视辅助!德普之星透视辅助软件... 透视辅助!德普之星透视辅助软件激活码,德普之星透视软件免费入口官网,2024教程(有挂解密)德普之星...
透视线上!wepoker有没有... 透视线上!wepoker有没有挂(透视)本来是有挂(教你教程)1、该软件可以轻松地帮助玩家将wepo...
透视有挂!德普之星透视辅助软件... 透视有挂!德普之星透视辅助软件,德普之星辅助器怎么用,曝光教程(有挂攻略)1)德普之星辅助器怎么用辅...
透视中牌率!黑侠破解wepok... 透视中牌率!黑侠破解wepoker(透视)一贯是真的有挂(2025新版教程);1、玩家可以在黑侠破解...
透视教程!德普之星透视辅助软件... 透视教程!德普之星透视辅助软件下载,德扑之心免费透视,分享教程(有挂技巧)1、这是跨平台的德普之星透...
透视线上!wepokerplu... 透视线上!wepokerplus透视脚本免费(透视)一贯真的是有挂(解密教程);小薇(透视辅助)致您...
透视挂!德普之星辅助工具如何打... 透视挂!德普之星辅助工具如何打开,德扑圈有透视吗,软件教程(有挂解说)是一款可以让一直输的玩家,快速...
辅助透视!wepoker私人局... 辅助透视!wepoker私人局辅助(透视)果然真的有挂(总结教程)一、wepoker私人局辅助AI软...
透视app!德普之星透视辅助,... 透视app!德普之星透视辅助,德普之星透视软件免费入口官网,2024教程(有挂规律)1、超多福利:超...
透视游戏!wepoker透视脚... 透视游戏!wepoker透视脚本是什么(透视)其实存在有挂(德州论坛)1、该软件可以轻松地帮助玩家将...