自定义webIpad证件相机(webRTC)

avatar
作者
筋斗云
阅读量:0
该技术方案可用于各浏览器自定义相机开发

相机UI(index.html)

<!DOCTYPE html> <html lang="zh" prew="-1">  <head>     <meta charset="UTF-8">     <meta name="viewport"         content="user-scalable=no, initial-scale=1, maximum-scale=5, minimum-scale=1, width=device-width" />     <title>自定义相机</title>     <link rel="stylesheet" href="./style.css">     <script src="./tools.js"></script>     <script src="./index.js"></script> </head>  <body>     <div class="errTip">         <p>Failed to obtain the rear camera of the device. Please try another solution to obtain resources!</p>         <button class="errBtn">GO Back</button>     </div>     <div class="takeOffTip"></div>     <div class="imgBoxDom">         <div class="imgBox">             <img src="./center.png" style="width: 4vw;">         </div>     </div>     <div class="rightBtnBox">         <div class="takeBtn"></div>         <div class="cancleBtn btn"></div>     </div>     <div class="bottomBtnBox">         <div class="reTakeBtn btn bottonSize"></div>         <div class="nextBtn btn bottonSize"></div>     </div>     <div class="loading-css">         Loading...     </div> </body>  </html>

 相机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)
 

<!DOCTYPE html> <html lang="zh">  <head>     <meta charset="UTF-8">     <meta name="viewport"         content="user-scalable=no, initial-scale=1, maximum-scale=5, minimum-scale=1, width=device-width" />     <title>调试相机</title>     <style>         * {             margin: 0;             padding: 0;             box-sizing: border-box;             border: 0;         }          img {             max-width: 100%;         }          .btnList {             padding: 10px;         }          label[type='file'],         button {             padding: 0 10px;             height: 32px;             line-height: 32px;             display: inline-block;             font-size: 14px;             appearance: auto;             border: 1px solid #999;             background-color: #dcdcdc;         }          label>input {             font-size: 0;             width: 0;             height: 0;             overflow: hidden;         }          .showImg {             padding: 5px;             display: flex;             flex-wrap: wrap;          }          .showImg>.box {             width: 33.33%;             padding: 5px;         }          .showImg>.box>.img {             width: 100%;             height: 20vw;             overflow: hidden;             border-radius: 10px;             border: 2px solid #888;         }          .showImg>.box>.img>img {             width: 100%;             height: 100%;             object-fit: cover;         }          html,         body {             height: 100%;             height: 100%;         }          body {             display: flex;             flex-direction: column;         }          .showImg {             flex: 1;             overflow-x: hidden;         }     </style> </head>  <body>     <div class="btnList">         <button onclick="openCarema('HK_ID')">COMM_ID_IMG</button>         <button onclick="openCarema('LANDING')">LANDING_IMG</button>         <label name="upload" type="file">             LOCAL_IMG             <input type="file" id="upload">         </label>     </div>     <div class="showImg" id="showImg"></div> </body> <script>     function fileToBase64(file) {         return new Promise((resolve, reject) => {             // 创建一个新的 FileReader 对象             var reader = new FileReader();             // 读取 File 对象             reader.readAsDataURL(file);             // 加载完成后             reader.onload = function () {                 // 将读取的数据转换为 base64 编码的字符串                 var base64String = reader.result.split(",")[1];                 // 解析为 Promise 对象,并返回 base64 编码的字符串                 resolve(base64String);             };              // 加载失败时             reader.onerror = function () {                 reject(new Error("Failed to load file"));             };         });     }     function showImg(url) {         var showImgDom = document.getElementById('showImg');         var img = document.createElement('img');         img.src = `data:image/jpeg;base64,${url}`;         var div = document.createElement('div');         var cDiv = document.createElement('div');         div.append(cDiv);         cDiv.append(img);         div.className = 'box';         cDiv.className = "img";         showImgDom.insertBefore(div, showImgDom.firstChild);     }     document.getElementById('upload').addEventListener('change', function ($event) {         var file = $event.target.files[0];         fileToBase64(file).then(showImg);     })     function openCarema(idType) {         var openId = Date.now() + '';         window.open(`./index.html?openId=${openId}&idType=${idType}&isDev=1`);         window.addEventListener('message', function (res) {             var resOpenId = res.data.openId;             var mothod = res.data.mothod;             var file = res.data.imgUrl;             console.log(resOpenId, mothod, file);             if (mothod === "success_file" && openId === resOpenId) fileToBase64(file).then(showImg);         })     } </script>  </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" });     } } 

 文件目录

效果图

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!