├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── images ├── Donal_Thrump_White_House.jpg ├── arrow-left.png ├── arrow-right.png ├── full-mask-1.png ├── full-mask-2.png ├── full-mask-3.png ├── full-mask-4.png ├── half-mask-0.png ├── half-mask-1.png ├── half-mask-2.png ├── half-mask-3.png ├── half-mask-4.png ├── half-mask-5.png ├── half-mask-6.png └── mesh_map.jpg ├── index.html ├── js ├── face-mask.js └── webcam-ui-lib.js └── style └── face-mask.css /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: bensonruan 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | images/benson.jpg 2 | images/face-mask-auto-detection-demo.jpg 3 | images/face-mask-demo.gif 4 | images/facemesh_mask_webcam_demo_short.gif 5 | images/facemesh_mask_webcam_demo.gif 6 | images/Mask_for_Donald_Trump.gif 7 | images/Mask_for_Trump.gif 8 | images/mesh_map_key_points.jpg 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Benson Ruan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Face-Mask 2 | Image and Real time webcam face detection, protect yourself from COVID19 with a virtual face mask. 3 | 4 | Utilize tensorflowjs facemesh model. 5 | 6 | ## Live Demo 7 | **[https://bensonruan.com/face-detection-javascript-real-time-mask-application/](https://bensonruan.com/face-detection-javascript-real-time-mask-application/)** 8 | 9 | ![mask-for-Trump](https://bensonruan.com/wp-content/uploads/2020/05/Mask_for_Trump.gif) 10 | 11 | 12 | ## Installing 13 | Clone this repository to your local computer 14 | ``` bash 15 | git https://github.com/bensonruan/Face-Mask.git 16 | ``` 17 | Point your localhost to the cloned root directory 18 | 19 | Browse to http://localhost/index.html 20 | 21 | ## Face Keypoints 22 | The facemesh detected keypoints that used for overlay the mask: 23 | * Forehead : 10 24 | * Left Cheek : 234 25 | * Chin : 152 26 | * Right Cheek : 454 27 | 28 | ![face-landmarks](https://bensonruan.com/wp-content/webp-express/webp-images/uploads/2020/05/mesh_map_key_points-1024x931.jpg.webp) 29 | 30 | ## Put Mask On 31 | * Click on 'Put Mask On' button to detect face on image and cover by mask 32 | * Turn on the Webcam switch and allowing the browser to access your webcam 33 | * Wait for a few seconds to Load Model for face landmark detection 34 | * Choose the face mask you would like to try on, watch yourself cover up 35 | 36 | ![facemesh-mask-demo](https://bensonruan.com/wp-content/webp-express/webp-images/uploads/2020/05/facemesh_mask_webcam_demo.webp) 37 | 38 | ## Notes 39 | * Please note that on iOS Safari, cameras can only be accessed via the https protocol 40 | * Facemesh model is designed for front-facing cameras on mobile devices, where faces in view tend to occupy a relatively large fraction of the canvas. MediaPipe Facemesh may struggle to identify far-away faces. 41 | 42 | ## Library 43 | * [jquery](https://code.jquery.com/jquery-3.3.1.min.js) - JQuery 44 | * [webcam-easy.js](https://github.com/bensonruan/webcam-easy) - javascript library for accessing webcam stream and taking photos 45 | * [facemesh](https://github.com/tensorflow/tfjs-models/tree/master/facemesh) - MediaPipe Facemesh is a lightweight machine learning pipeline predicting 486 3D facial landmarks to infer the approximate surface geometry of a human face 46 | 47 | ## Support me 48 | [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/W7W6METMY) 49 | -------------------------------------------------------------------------------- /images/Donal_Thrump_White_House.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bensonruan/Face-Mask/6703ecfc56bd3b0131cafb656b8532332690186c/images/Donal_Thrump_White_House.jpg -------------------------------------------------------------------------------- /images/arrow-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bensonruan/Face-Mask/6703ecfc56bd3b0131cafb656b8532332690186c/images/arrow-left.png -------------------------------------------------------------------------------- /images/arrow-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bensonruan/Face-Mask/6703ecfc56bd3b0131cafb656b8532332690186c/images/arrow-right.png -------------------------------------------------------------------------------- /images/full-mask-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bensonruan/Face-Mask/6703ecfc56bd3b0131cafb656b8532332690186c/images/full-mask-1.png -------------------------------------------------------------------------------- /images/full-mask-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bensonruan/Face-Mask/6703ecfc56bd3b0131cafb656b8532332690186c/images/full-mask-2.png -------------------------------------------------------------------------------- /images/full-mask-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bensonruan/Face-Mask/6703ecfc56bd3b0131cafb656b8532332690186c/images/full-mask-3.png -------------------------------------------------------------------------------- /images/full-mask-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bensonruan/Face-Mask/6703ecfc56bd3b0131cafb656b8532332690186c/images/full-mask-4.png -------------------------------------------------------------------------------- /images/half-mask-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bensonruan/Face-Mask/6703ecfc56bd3b0131cafb656b8532332690186c/images/half-mask-0.png -------------------------------------------------------------------------------- /images/half-mask-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bensonruan/Face-Mask/6703ecfc56bd3b0131cafb656b8532332690186c/images/half-mask-1.png -------------------------------------------------------------------------------- /images/half-mask-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bensonruan/Face-Mask/6703ecfc56bd3b0131cafb656b8532332690186c/images/half-mask-2.png -------------------------------------------------------------------------------- /images/half-mask-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bensonruan/Face-Mask/6703ecfc56bd3b0131cafb656b8532332690186c/images/half-mask-3.png -------------------------------------------------------------------------------- /images/half-mask-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bensonruan/Face-Mask/6703ecfc56bd3b0131cafb656b8532332690186c/images/half-mask-4.png -------------------------------------------------------------------------------- /images/half-mask-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bensonruan/Face-Mask/6703ecfc56bd3b0131cafb656b8532332690186c/images/half-mask-5.png -------------------------------------------------------------------------------- /images/half-mask-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bensonruan/Face-Mask/6703ecfc56bd3b0131cafb656b8532332690186c/images/half-mask-6.png -------------------------------------------------------------------------------- /images/mesh_map.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bensonruan/Face-Mask/6703ecfc56bd3b0131cafb656b8532332690186c/images/mesh_map.jpg -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Face Mask 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 | 28 |
29 |
30 | 31 |
32 |
33 | Loading Model 34 |
35 | 36 |
37 |
38 | 39 |
40 | 41 |
42 | 43 |
44 | 45 |
46 |
    47 |
  • 48 |
  • 49 |
  • 50 |
  • 51 |
  • 52 |
  • 53 |
  • 54 |
  • 55 |
  • 56 |
  • 57 |
58 |
59 | 60 |
61 |
62 | 63 | 64 |
65 | Fail to start camera, please allow permision to access camera.
66 | If you are browsing through social media built in browsers, you would need to open the page in Sarafi (iPhone)/ Chrome (Android) 67 | 68 |
69 |
70 |
71 |
72 | 73 |
74 |
75 |
76 |
77 |
78 | 79 | 80 | -------------------------------------------------------------------------------- /js/face-mask.js: -------------------------------------------------------------------------------- 1 | const webcamElement = document.getElementById('webcam'); 2 | const canvasElement = document.getElementById('canvas'); 3 | const imageElement = document.getElementById('faces'); 4 | const webcam = new Webcam(webcamElement, 'user'); 5 | let selectedMask = $(".selected-mask img"); 6 | let isVideo = false; 7 | let model = null; 8 | let cameraFrame = null; 9 | let detectFace = false; 10 | let clearMask = false; 11 | let maskOnImage = false; 12 | let masks = []; 13 | let maskKeyPointIndexs = [10, 234, 152, 454]; //overhead, left Cheek, chin, right cheek 14 | 15 | 16 | $("#webcam-switch").change(function () { 17 | if(this.checked){ 18 | $('.md-modal').addClass('md-show'); 19 | webcam.start() 20 | .then(result =>{ 21 | isVideo = true; 22 | cameraStarted(); 23 | switchSource(); 24 | console.log("webcam started"); 25 | maskOnImage = false; 26 | startFaceMask(); 27 | }) 28 | .catch(err => { 29 | displayError(); 30 | }); 31 | } 32 | else { 33 | webcam.stop(); 34 | if(cameraFrame!= null){ 35 | clearMask = true; 36 | detectFace = false; 37 | cancelAnimationFrame(cameraFrame); 38 | } 39 | isVideo = false; 40 | switchSource(); 41 | cameraStopped(true); 42 | console.log("webcam stopped"); 43 | } 44 | }); 45 | 46 | $("#arrowLeft").click(function () { 47 | let itemWidth = parseInt($("#mask-list ul li").css("width")) 48 | + parseInt($("#mask-list ul li").css("margin-left")) 49 | + parseInt($("#mask-list ul li").css("margin-right")); 50 | let marginLeft = parseInt($("#mask-list ul").css("margin-left")); 51 | $("#mask-list ul").css({"margin-left": (marginLeft+itemWidth) +"px", "transition": "0.3s"}); 52 | }); 53 | 54 | $("#arrowRight").click(function () { 55 | let itemWidth = parseInt($("#mask-list ul li").css("width")) 56 | + parseInt($("#mask-list ul li").css("margin-left")) 57 | + parseInt($("#mask-list ul li").css("margin-right")); 58 | let marginLeft = parseInt($("#mask-list ul").css("margin-left")); 59 | $("#mask-list ul").css({"margin-left": (marginLeft-itemWidth) +"px", "transition": "0.3s"}); 60 | }); 61 | 62 | $("#mask-list ul li").click(function () { 63 | $(".selected-mask").removeClass("selected-mask"); 64 | $(this).addClass("selected-mask"); 65 | selectedMask = $(".selected-mask img"); 66 | clearCanvas(); 67 | if(model !=null && isVideo == false && maskOnImage){ 68 | detectFaces(); 69 | } 70 | }); 71 | 72 | $("#mask-btn").click(function () { 73 | $("#canvas").css({width: imageElement.clientWidth, height: imageElement.clientHeight}); 74 | startFaceMask() 75 | .then(res => { 76 | maskOnImage = true; 77 | detectFaces(); 78 | }); 79 | }); 80 | 81 | $('#closeError').click(function() { 82 | $("#webcam-switch").prop('checked', false).change(); 83 | }); 84 | 85 | async function startFaceMask() { 86 | return new Promise((resolve, reject) => { 87 | $(".loading").removeClass('d-none'); 88 | if(model == null){ 89 | faceLandmarksDetection.load(faceLandmarksDetection.SupportedPackages.mediapipeFacemesh).then(mdl => { 90 | model = mdl; 91 | console.log("model loaded"); 92 | if(isVideo && webcam.facingMode == 'user'){ 93 | detectFace = true; 94 | } 95 | cameraFrame = detectFaces().then(() => { 96 | $(".loading").addClass('d-none'); 97 | resolve(); 98 | }); 99 | }) 100 | .catch(error => { 101 | displayError('Fail to load face mesh model
Please refresh the page to try again'); 102 | reject(error); 103 | }); 104 | }else if(!isVideo){ 105 | cameraFrame = detectFaces().then(() => { 106 | $(".loading").addClass('d-none'); 107 | resolve(); 108 | }); 109 | } 110 | }); 111 | } 112 | 113 | $("#webcam").bind("loadedmetadata", function () { 114 | if(isVideo && webcam.facingMode == 'user'){ 115 | detectFace = true; 116 | resizeCanvas(); 117 | } 118 | if(model != null){ 119 | cameraFrame = detectFaces().then(() => { 120 | $(".loading").addClass('d-none'); 121 | }); 122 | } 123 | }); 124 | 125 | async function detectFaces() { 126 | let inputElement = isVideo? webcamElement : imageElement; 127 | let flipHorizontal = isVideo; 128 | await model.estimateFaces 129 | ({ 130 | input: inputElement, 131 | returnTensors: false, 132 | flipHorizontal: flipHorizontal, 133 | predictIrises: false 134 | }).then(predictions => { 135 | //console.log(predictions); 136 | let confident_predictions = $.grep(predictions, function(p) { 137 | return p.faceInViewConfidence > 0.5; 138 | }); 139 | drawMask(confident_predictions); 140 | if(clearMask){ 141 | clearCanvas(); 142 | clearMask = false; 143 | } 144 | if(detectFace){ 145 | cameraFrame = requestAnimFrame(detectFaces); 146 | } 147 | }); 148 | } 149 | 150 | function drawMask(predictions){ 151 | if(masks.length != predictions.length){ 152 | clearCanvas(); 153 | } 154 | overheadIndex = 0; 155 | chinIndex = 2; 156 | if(isVideo){ 157 | leftCheekIndex = 3; 158 | rightCheekIndex = 1; 159 | } 160 | else{ 161 | leftCheekIndex = 1; 162 | rightCheekIndex = 3; 163 | } 164 | if (predictions.length > 0) { 165 | for (let x = 0; x < predictions.length; x++) { 166 | const keypoints = predictions[x].scaledMesh; //468 key points of face; 167 | 168 | if(masks.length > x){ 169 | dots = masks[x].keypoints; 170 | maskElement = masks[x].maskElement; 171 | } 172 | else{ 173 | dots = []; 174 | maskElement = $(""); 175 | masks.push({ 176 | keypoints: dots, 177 | maskElement: maskElement 178 | }); 179 | maskElement.appendTo($("#canvas")); 180 | } 181 | for (let i = 0; i < maskKeyPointIndexs.length; i++) { 182 | const coordinate = getCoordinate(keypoints[maskKeyPointIndexs[i]][0], keypoints[maskKeyPointIndexs[i]][1]); 183 | if(dots.length > i){ 184 | dot = dots[i]; 185 | } 186 | else{ 187 | dotElement = $("
"); 188 | //dotElement.appendTo($("#canvas")); 189 | dot = {top:0, left:0, element: dotElement}; 190 | dots.push(dot); 191 | } 192 | dot.left = coordinate[0]; 193 | dot.top = coordinate[1]; 194 | dot.element.css({top:dot.top, left:dot.left, position:'absolute'}); 195 | } 196 | maskType = selectedMask.attr("data-mask-type"); 197 | switch(maskType) { 198 | case 'full': 199 | maskCoordinate= {top: dots[overheadIndex].top, left: dots[leftCheekIndex].left}; 200 | maskHeight = (dots[chinIndex].top - dots[overheadIndex].top) ; 201 | break; 202 | case 'half': 203 | default: 204 | maskCoordinate = dots[leftCheekIndex]; 205 | maskHeight = (dots[chinIndex].top - dots[leftCheekIndex].top) ; 206 | break; 207 | } 208 | maskWidth = (dots[rightCheekIndex].left - dots[leftCheekIndex].left) ; 209 | maskSizeAdjustmentWidth = parseFloat(selectedMask.attr("data-scale-width")); 210 | maskSizeAdjustmentHeight = parseFloat(selectedMask.attr("data-scale-height")); 211 | maskSizeAdjustmentTop = parseFloat(selectedMask.attr("data-top-adj")); 212 | if(isVideo){ 213 | maskSizeAdjustmentLeft = parseFloat(selectedMask.attr("data-left-adj")); 214 | } 215 | else{ 216 | maskSizeAdjustmentLeft = 0; 217 | } 218 | 219 | 220 | maskTop = maskCoordinate.top - ((maskHeight * (maskSizeAdjustmentHeight-1))/2) - (maskHeight * maskSizeAdjustmentTop); 221 | maskLeft = maskCoordinate.left - ((maskWidth * (maskSizeAdjustmentWidth-1))/2) - (maskWidth * maskSizeAdjustmentLeft); 222 | 223 | maskElement.css({ 224 | top: maskTop, 225 | left: maskLeft, 226 | width: maskWidth * maskSizeAdjustmentWidth, 227 | height: maskHeight * maskSizeAdjustmentHeight, 228 | position:'absolute' 229 | }); 230 | } 231 | } 232 | } 233 | 234 | function getCoordinate(x,y){ 235 | if(isVideo){ 236 | if(window.innerWidth/window.innerHeight >= webcamElement.width/webcamElement.height){ 237 | ratio = canvasElement.clientHeight/webcamElement.height; 238 | resizeX = x*ratio; 239 | resizeY = y*ratio; 240 | }else{ 241 | leftAdjustment = webcamElement.width - canvasElement.clientWidth; 242 | resizeX = x - leftAdjustment; 243 | resizeY = y; 244 | } 245 | return [resizeX, resizeY]; 246 | } 247 | else{ 248 | return [x, y]; 249 | } 250 | } 251 | 252 | function clearCanvas(){ 253 | $("#canvas").empty(); 254 | masks = []; 255 | } 256 | 257 | function switchSource(){ 258 | if(isVideo){ 259 | containerElement = $("#webcam-container"); 260 | $("#button-control").addClass("d-none"); 261 | }else{ 262 | canvasElement.style.transform =""; 263 | containerElement = $("#image-container"); 264 | $("#button-control").removeClass("d-none"); 265 | $("#canvas").css({width: imageElement.clientWidth, height: imageElement.clientHeight}); 266 | } 267 | $("#canvas").appendTo(containerElement); 268 | $(".loading").appendTo(containerElement); 269 | $("#mask-slider").appendTo(containerElement); 270 | clearCanvas(); 271 | } 272 | 273 | $(window).resize(function() { 274 | resizeCanvas(); 275 | }); -------------------------------------------------------------------------------- /js/webcam-ui-lib.js: -------------------------------------------------------------------------------- 1 | function displayError(err = ''){ 2 | if(err!=''){ 3 | $("#errorMsg").html(err); 4 | } 5 | $("#errorMsg").removeClass("d-none"); 6 | } 7 | 8 | function cameraStarted(){ 9 | $("#errorMsg").addClass("d-none"); 10 | $("#webcam-caption").html("on"); 11 | $("#webcam-control").removeClass("webcam-off"); 12 | $("#webcam-control").addClass("webcam-on"); 13 | $(".webcam-container").removeClass("d-none"); 14 | $("#wpfront-scroll-top-container").addClass("d-none"); 15 | window.scrollTo(0, 0); 16 | $('body').css('overflow-y','hidden'); 17 | } 18 | 19 | function cameraStopped(doScroll = false){ 20 | $("#errorMsg").addClass("d-none"); 21 | $("#wpfront-scroll-top-container").removeClass("d-none"); 22 | $("#webcam-control").removeClass("webcam-on"); 23 | $("#webcam-control").addClass("webcam-off"); 24 | $(".webcam-container").addClass("d-none"); 25 | $("#webcam-caption").html("Click to Start Camera"); 26 | $('.md-modal').removeClass('md-show'); 27 | if(doScroll){ 28 | $('body').css('overflow-y','scroll'); 29 | $([document.documentElement, document.body]).animate({ 30 | scrollTop: ($("#face-mask-app").offset().top - 80) 31 | }, 1000); } 32 | } 33 | 34 | function resizeCanvas(canvasID ="canvas"){ 35 | if(webcamElement != null && webcamElement.scrollHeight>0){ 36 | $("#"+canvasID).css({width: webcamElement.scrollWidth, height: webcamElement.scrollHeight}); 37 | } 38 | } 39 | 40 | function beforeTakePhoto(){ 41 | $('.flash') 42 | .show() 43 | .animate({opacity: 0.3}, 500) 44 | .fadeOut(500) 45 | .css({'opacity': 0.7}); 46 | window.scrollTo(0, 0); 47 | $('#webcam-control').addClass('d-none'); 48 | $('#cameraControls').addClass('d-none'); 49 | } 50 | 51 | function afterTakePhoto(){ 52 | $('#canvas').removeClass('d-none'); 53 | $('#take-photo').addClass('d-none'); 54 | $('#exit-app').removeClass('d-none'); 55 | $('#download-photo').removeClass('d-none'); 56 | $('#resume-camera').removeClass('d-none'); 57 | $('#cameraControls').removeClass('d-none'); 58 | } 59 | 60 | function removeCapture(){ 61 | $('#canvas').addClass('d-none'); 62 | $('#webcam-control').removeClass('d-none'); 63 | $('#cameraControls').removeClass('d-none'); 64 | $('#take-photo').removeClass('d-none'); 65 | $('#exit-app').addClass('d-none'); 66 | $('#download-photo').addClass('d-none'); 67 | $('#resume-camera').addClass('d-none'); 68 | } 69 | 70 | window.requestAnimFrame = (function(){ 71 | return window.requestAnimationFrame || 72 | window.webkitRequestAnimationFrame || 73 | window.mozRequestAnimationFrame || 74 | window.oRequestAnimationFrame || 75 | window.msRequestAnimationFrame || 76 | function( callback ){ 77 | window.setTimeout(callback, 1000 / 60); 78 | }; 79 | })(); 80 | 81 | window.cancelAnimationFrame = (function(){ 82 | return window.cancelAnimationFrame || window.mozCancelAnimationFrame; 83 | })(); 84 | -------------------------------------------------------------------------------- /style/face-mask.css: -------------------------------------------------------------------------------- 1 | #face-mask-app { 2 | background-position: center center; 3 | background-repeat: no-repeat; 4 | background-size: cover; 5 | background-color: black; 6 | width: 100%; 7 | height: 100vh; 8 | } 9 | 10 | #image-container{ 11 | position: relative; 12 | margin-top: -55px; 13 | width: 100%; 14 | height: 100%; 15 | overflow: hidden; 16 | text-align: center; 17 | background-color: black; 18 | } 19 | #faces{ 20 | width: auto; 21 | height: 100%; 22 | max-width: none; 23 | } 24 | 25 | #button-control{ 26 | position: relative; 27 | bottom: 200px; 28 | left: 0; 29 | right: 0; 30 | height: 65px; 31 | margin: auto; 32 | background: transparent; 33 | border: 0; 34 | width: 320px; 35 | z-index: 99999; 36 | } 37 | 38 | #mask-btn{ 39 | font-size: 30px; 40 | padding: 10px 30px; 41 | font-weight: 400; 42 | opacity: 0.8; 43 | position: absolute; 44 | margin: auto; 45 | left: 0; 46 | right: 0; 47 | } 48 | 49 | .form-control.webcam-start{ 50 | top: 10%!important; 51 | } 52 | 53 | .form-control.webcam-on{ 54 | top: 15%!important; 55 | } 56 | 57 | #canvas{ 58 | z-index: 9998!important; 59 | } 60 | 61 | .loading{ 62 | position: absolute; 63 | top: 0; 64 | bottom: 0; 65 | left: 0; 66 | right: 0; 67 | z-index:300000; 68 | border: white 1px; 69 | color: white; 70 | padding: 75px 26px; 71 | font-size: 22px; 72 | margin: auto; 73 | width: 200px; 74 | height: 200px; 75 | } 76 | 77 | .spinner-border { 78 | position: absolute; 79 | top: 0; 80 | left: 0; 81 | width: 200px; 82 | height: 200px; 83 | color: white; 84 | z-index:300000; 85 | filter: alpha(opacity=80); 86 | -moz-opacity: 0.8; 87 | opacity: 0.8; 88 | } 89 | 90 | .dot { 91 | height: 10px; 92 | width: 10px; 93 | background-color: blue; 94 | border-radius: 50%; 95 | display: inline-block; 96 | z-index: 999999; 97 | } 98 | 99 | #arrowLeft{ 100 | position: absolute; 101 | left: 10px; 102 | top: 0; 103 | bottom: 0; 104 | z-index: 999999; 105 | opacity: 0.7; 106 | cursor: pointer; 107 | margin: auto; 108 | } 109 | 110 | #arrowRight{ 111 | position: absolute; 112 | right: 10px; 113 | top: 0; 114 | bottom: 0; 115 | z-index: 999999; 116 | opacity: 0.7; 117 | cursor: pointer; 118 | margin: auto; 119 | } 120 | 121 | #mask-slider{ 122 | width: 100%; 123 | height: 125px; 124 | margin: auto; 125 | position: relative; 126 | bottom: 190px; 127 | background-color: black; 128 | opacity: 0.7; 129 | left: 0; 130 | right: 0; 131 | z-index: 9999; 132 | } 133 | 134 | #mask-list{ 135 | position: relative; 136 | width: 85%; 137 | height: 125px; 138 | margin: auto; 139 | overflow: hidden; 140 | } 141 | 142 | #mask-slider ul{ 143 | list-style: none; 144 | width: 10000px; 145 | height: 125px; 146 | padding-left: 10px; 147 | margin-bottom: 0; 148 | margin-top: 10px; 149 | } 150 | 151 | #mask-slider ul li { 152 | width: 100px; 153 | height: 100px; 154 | float: left; 155 | margin: 5px 15px; 156 | vertical-align: middle; 157 | text-align: center; 158 | cursor: pointer; 159 | } 160 | 161 | #mask-slider ul li img { 162 | width: 85px; 163 | margin: auto; 164 | } 165 | 166 | .mask{ 167 | border: 0px; 168 | } 169 | 170 | .selected-mask{ 171 | border: 5px solid #0099cc; 172 | } 173 | 174 | .full-mask{ 175 | height: 85px!important; 176 | width: auto!important; 177 | } 178 | 179 | @media screen and (max-width: 1024px) { 180 | .loading{ 181 | padding: 55px 0px; 182 | font-size: medium; 183 | width: 150px; 184 | } 185 | .spinner-border{ 186 | width: 150px; 187 | height: 150px; 188 | } 189 | #mask-slider{ 190 | width: 100%; 191 | } 192 | #mask-list { 193 | width: 80%; 194 | } 195 | #mask-list ul { 196 | padding-left: 0; 197 | margin-top: 25px; 198 | } 199 | #mask-list ul li { 200 | width: 75px; 201 | height: 75px; 202 | margin: 5px 8px; 203 | } 204 | #mask-list ul li img { 205 | width: 65px; 206 | } 207 | 208 | .full-mask{ 209 | height: 65px!important; 210 | width: auto!important; 211 | margin-left: 3px!important; 212 | } 213 | 214 | #mask-btn{ 215 | font-size: 28px; 216 | } 217 | } 218 | 219 | @media screen and (max-width: 767px) { 220 | #face-mask-app{ 221 | height: 100vh; 222 | } 223 | #arrowLeft{ 224 | height: 43px; 225 | } 226 | #arrowRight{ 227 | height: 43px; 228 | } 229 | } 230 | 231 | @media screen and (min-width: 450px) and (max-width: 767px) { 232 | .form-control.webcam-start{ 233 | top: 0!important; 234 | } 235 | 236 | #mask-list { 237 | width: 86%; 238 | } 239 | } 240 | 241 | @media screen and (max-width: 450px) { 242 | .form-control.webcam-start{ 243 | top: 5%!important; 244 | } 245 | .form-control.webcam-on{ 246 | top: 15%!important; 247 | } 248 | #mask-slider{ 249 | bottom: 320px; 250 | } 251 | #webcam-container #mask-slider{ 252 | bottom: 70px; 253 | } 254 | } 255 | 256 | .md-modal { 257 | margin: 0!important; 258 | } 259 | 260 | 261 | #webcam-container #mask-slider{ 262 | position: fixed; 263 | width: 100%; 264 | margin: 0 0 0 auto; 265 | } 266 | 267 | @media screen and (min-width: 451px) and (max-width: 892px) { 268 | #webcam-container #mask-slider{ 269 | bottom: 30px; 270 | } 271 | } 272 | 273 | @media screen and (min-width: 892px) { 274 | #webcam-container #mask-slider{ 275 | bottom: 0px; 276 | } 277 | } --------------------------------------------------------------------------------