├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── public ├── effects │ ├── Emotion_Meter.deepar │ ├── Emotions_Exaggerator.deepar │ ├── Fire_Effect.deepar │ ├── Hope.deepar │ ├── Humanoid.deepar │ ├── MakeupLook.deepar │ ├── Neon_Devil_Horns.deepar │ ├── Ping_Pong.deepar │ ├── Pixel_Hearts.deepar │ ├── Snail.deepar │ ├── Split_View_Look.deepar │ ├── Stallone.deepar │ ├── Vendetta_Mask.deepar │ ├── flower_face.deepar │ ├── galaxy_background_web.deepar │ ├── ray-ban-wayfarer.deepar │ └── viking_helmet.deepar ├── images │ ├── bg-grid-dark.svg │ ├── crystal.png │ ├── gradient.svg │ └── powered-by.svg ├── index.html ├── style.css └── thumbs │ ├── devil_horns.png │ ├── fire.png │ ├── flower_face.png │ ├── galaxy.png │ ├── hope.png │ ├── humanoid.png │ ├── makeup-split.png │ ├── makeup.png │ ├── ping_pong.png │ ├── pixel_hearts.png │ ├── ray-ban-wayfarer.png │ ├── snail.png │ ├── stallone.png │ ├── vendetta.png │ └── viking.png ├── src ├── carousel.js └── index.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | package-lock.json 4 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 DeepAR SDK 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 | # quickstart-web-npm-js 2 | 3 | This is a quickstart project for DeepAR Web SDK. It is a minimal example of how to use the SDK. 4 | 5 | ## View the demo 6 | 7 | [https://demo.deepar.ai/quickstart-web-js-npm/](https://demo.deepar.ai/quickstart-web-js-npm/) 8 | 9 | ## How to run the project 10 | 11 | - Go to https://developer.deepar.ai 12 | - Sign up 13 | - Create a project and a Web app 14 | - Copy the license key 15 | - Paste it into `src/index.js` (replace `your_license_key_goes_here`) 16 | - Open the terminal in the root of the project 17 | - Run `npm install` 18 | - Run `npm run dev` 19 | - If the browser doesn't open automatically, open http://localhost:8888 20 | 21 | ## How to build the project 22 | 23 | - Open the terminal in the root of the project 24 | - Run `npm install` 25 | - Run `npm run build` 26 | - The build will be in the `dist` folder 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quickstart-web-js-npm", 3 | "version": "1.0.0", 4 | "description": "Quickstart project for DeepAR Web.", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "rm -rf dist && webpack --config webpack.config.js --mode production && cp -r public/* dist && cp -r node_modules/deepar dist/deepar-resources", 8 | "dev": "webpack serve --mode development --open --port 8888" 9 | }, 10 | "author": "", 11 | "license": "MIT", 12 | "devDependencies": { 13 | "webpack": "^5.74.0", 14 | "webpack-cli": "^4.10.0", 15 | "webpack-dev-server": "^4.11.0" 16 | }, 17 | "dependencies": { 18 | "deepar": "^5.4.1" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /public/effects/Emotion_Meter.deepar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/effects/Emotion_Meter.deepar -------------------------------------------------------------------------------- /public/effects/Emotions_Exaggerator.deepar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/effects/Emotions_Exaggerator.deepar -------------------------------------------------------------------------------- /public/effects/Fire_Effect.deepar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/effects/Fire_Effect.deepar -------------------------------------------------------------------------------- /public/effects/Hope.deepar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/effects/Hope.deepar -------------------------------------------------------------------------------- /public/effects/Humanoid.deepar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/effects/Humanoid.deepar -------------------------------------------------------------------------------- /public/effects/MakeupLook.deepar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/effects/MakeupLook.deepar -------------------------------------------------------------------------------- /public/effects/Neon_Devil_Horns.deepar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/effects/Neon_Devil_Horns.deepar -------------------------------------------------------------------------------- /public/effects/Ping_Pong.deepar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/effects/Ping_Pong.deepar -------------------------------------------------------------------------------- /public/effects/Pixel_Hearts.deepar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/effects/Pixel_Hearts.deepar -------------------------------------------------------------------------------- /public/effects/Snail.deepar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/effects/Snail.deepar -------------------------------------------------------------------------------- /public/effects/Split_View_Look.deepar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/effects/Split_View_Look.deepar -------------------------------------------------------------------------------- /public/effects/Stallone.deepar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/effects/Stallone.deepar -------------------------------------------------------------------------------- /public/effects/Vendetta_Mask.deepar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/effects/Vendetta_Mask.deepar -------------------------------------------------------------------------------- /public/effects/flower_face.deepar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/effects/flower_face.deepar -------------------------------------------------------------------------------- /public/effects/galaxy_background_web.deepar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/effects/galaxy_background_web.deepar -------------------------------------------------------------------------------- /public/effects/ray-ban-wayfarer.deepar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/effects/ray-ban-wayfarer.deepar -------------------------------------------------------------------------------- /public/effects/viking_helmet.deepar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/effects/viking_helmet.deepar -------------------------------------------------------------------------------- /public/images/bg-grid-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /public/images/crystal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/images/crystal.png -------------------------------------------------------------------------------- /public/images/gradient.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 8 | 9 | 10 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /public/images/powered-by.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | DeepAR Web SDK - Face and body AR in the browser 11 | 12 | 13 | 14 | 15 | 16 |
17 |
25 | 31 |
41 |
42 |
43 | 48 |
49 |
50 | 51 | 52 | 70 | 71 | 72 | 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /public/style.css: -------------------------------------------------------------------------------- 1 | canvas.deepar { 2 | border: 0px none; 3 | background-color: black; 4 | display: block; 5 | margin: auto; 6 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 7 | max-width: 100%; 8 | max-height: 100%; 9 | } 10 | 11 | body { 12 | margin: 0px; 13 | padding: 0px; 14 | font-family: sans-serif; 15 | } 16 | 17 | #loading-screen, 18 | #permission-denied-screen, 19 | #permission-denied-background { 20 | background-color: rgb(17 24 39 / 1); 21 | background-image: url("./images/gradient.svg"), url("./images/bg-grid-dark.svg"); 22 | background-repeat: no-repeat, repeat; 23 | display: flex; 24 | align-items: center; 25 | justify-content: center; 26 | } 27 | 28 | .fixed-fullscreen { 29 | position: fixed; 30 | top: 0; 31 | left: 0; 32 | width: 100vw; 33 | height: 100vh; 34 | } 35 | 36 | /* Carousel */ 37 | 38 | .carousel { 39 | position: absolute; 40 | bottom: 0; 41 | overflow: hidden; 42 | cursor: grab; 43 | display: flex; 44 | align-items: center; 45 | justify-content: center; 46 | height: 120px; 47 | width: 100%; 48 | } 49 | 50 | .carousel.active { 51 | cursor: grabbing; 52 | } 53 | 54 | .carousel-slider { 55 | position: absolute; 56 | display: flex; 57 | flex-direction: row; 58 | align-items: center; 59 | justify-content: center; 60 | width: 100%; 61 | top: 0; 62 | left: 0; 63 | width: fit-content; 64 | height: inherit; 65 | transition: left 0.3s ease; 66 | will-change: transform; 67 | } 68 | 69 | .carousel.active .inner { 70 | transition: none; 71 | } 72 | 73 | .carousel-center { 74 | width: 84px; 75 | height: 84px; 76 | border: 8px solid #fff; 77 | border-radius: 50%; 78 | z-index: 100; 79 | box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.5); 80 | -webkit-user-select: none; 81 | } 82 | 83 | .carousel-slider .slide { 84 | width: 70px; 85 | height: 70px; 86 | display: flex; 87 | align-items: center; 88 | justify-content: center; 89 | border-radius: 50%; 90 | margin-left: 10px; 91 | margin-right: 10px; 92 | } 93 | 94 | .carousel-slider .slide.active { 95 | margin-left: 24px; 96 | margin-right: 24px; 97 | width: 80px; 98 | height: 80px; 99 | } 100 | 101 | .slide { 102 | overflow: hidden; 103 | border: 2px solid white; 104 | -webkit-touch-callout: none; /* Safari Touch */ 105 | -webkit-user-select: none; /* Webkit */ 106 | -moz-user-select: none; /* Firefox */ 107 | -ms-user-select: none; /* Edge*/ 108 | user-select: none; /* Future-proof*/ 109 | } 110 | .slide.active { 111 | background-color: #010c43; 112 | } 113 | .slide img { 114 | width: 100%; 115 | height: 100%; 116 | object-fit: cover; 117 | } 118 | 119 | /* Loading spinner */ 120 | 121 | .lds-ring { 122 | display: inline-block; 123 | position: absolute; 124 | top: 50%; 125 | left: 50%; 126 | transform: translate(-50%, -50%); 127 | width: 80px; 128 | height: 80px; 129 | } 130 | .lds-ring div { 131 | box-sizing: border-box; 132 | display: block; 133 | position: absolute; 134 | width: 64px; 135 | height: 64px; 136 | margin: 8px; 137 | border: 8px solid #fff; 138 | border-radius: 50%; 139 | animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; 140 | border-color: #fff transparent transparent transparent; 141 | } 142 | .lds-ring div:nth-child(1) { 143 | animation-delay: -0.45s; 144 | } 145 | .lds-ring div:nth-child(2) { 146 | animation-delay: -0.3s; 147 | } 148 | .lds-ring div:nth-child(3) { 149 | animation-delay: -0.15s; 150 | } 151 | 152 | @keyframes lds-ring { 153 | 0% { 154 | transform: rotate(0deg); 155 | } 156 | 100% { 157 | transform: rotate(360deg); 158 | } 159 | } 160 | 161 | /* Loading screen */ 162 | 163 | @keyframes bounce { 164 | 0%, 165 | 100% { 166 | transform: translateY(-25%); 167 | animation-timing-function: cubic-bezier(0.8, 0, 1, 1); 168 | } 169 | 50% { 170 | transform: none; 171 | animation-timing-function: cubic-bezier(0, 0, 0.2, 1); 172 | } 173 | } 174 | .animate-bounce { 175 | animation: bounce 1s infinite; 176 | } 177 | 178 | #loading-progress-bar { 179 | width: 0%; 180 | background: rgb(0 98 209 / 1); 181 | height: 100%; 182 | transition: width 5s ease-out; 183 | } 184 | 185 | /* Permission denied screen */ 186 | 187 | .permission-denied-text-container { 188 | position: absolute; 189 | left: 50%; 190 | top: 50%; 191 | transform: translateX(-50%) translateY(-50%); 192 | width: 100%; 193 | display: flex; 194 | align-items: center; 195 | justify-content: center; 196 | flex-direction: column; 197 | text-align: center; 198 | } 199 | 200 | .permission-denied-text { 201 | width: 400px; 202 | max-width: 95%; 203 | font-size: 16px; 204 | line-height: 1.2; 205 | color: #fff; 206 | } 207 | 208 | .permission-denied-button { 209 | margin-top: 20px; 210 | padding: 10px 20px; 211 | border-radius: 4px; 212 | background-color: #fff; 213 | color: #000; 214 | font-size: 16px; 215 | font-weight: 500; 216 | cursor: pointer; 217 | transition: background-color 0.2s ease; 218 | border: none; 219 | display: inline-block; 220 | text-decoration: none; 221 | width: fit-content; 222 | 223 | &:hover { 224 | background-color: #eaeaea; 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /public/thumbs/devil_horns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/thumbs/devil_horns.png -------------------------------------------------------------------------------- /public/thumbs/fire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/thumbs/fire.png -------------------------------------------------------------------------------- /public/thumbs/flower_face.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/thumbs/flower_face.png -------------------------------------------------------------------------------- /public/thumbs/galaxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/thumbs/galaxy.png -------------------------------------------------------------------------------- /public/thumbs/hope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/thumbs/hope.png -------------------------------------------------------------------------------- /public/thumbs/humanoid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/thumbs/humanoid.png -------------------------------------------------------------------------------- /public/thumbs/makeup-split.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/thumbs/makeup-split.png -------------------------------------------------------------------------------- /public/thumbs/makeup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/thumbs/makeup.png -------------------------------------------------------------------------------- /public/thumbs/ping_pong.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/thumbs/ping_pong.png -------------------------------------------------------------------------------- /public/thumbs/pixel_hearts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/thumbs/pixel_hearts.png -------------------------------------------------------------------------------- /public/thumbs/ray-ban-wayfarer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/thumbs/ray-ban-wayfarer.png -------------------------------------------------------------------------------- /public/thumbs/snail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/thumbs/snail.png -------------------------------------------------------------------------------- /public/thumbs/stallone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/thumbs/stallone.png -------------------------------------------------------------------------------- /public/thumbs/vendetta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/thumbs/vendetta.png -------------------------------------------------------------------------------- /public/thumbs/viking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeepARSDK/quickstart-web-js-npm/ce85dcfac04c35f46a1c7d44e2df21ecbb049b6a/public/thumbs/viking.png -------------------------------------------------------------------------------- /src/carousel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A draggable snap-like carousel, optimized to work smoothly across desktop and mobile devices. 3 | * 4 | */ 5 | 6 | class Carousel { 7 | /** 8 | * 9 | * @param {String} sliderID the ID to query on the page 10 | * @param {String} sliderClass the className of the slider inner 11 | * @param {String} slideClass the className of the slider slides 12 | * @returns 13 | */ 14 | constructor( 15 | sliderID, 16 | sliderClass = "carousel-slider", 17 | slideClass = "slide", 18 | centerID = "carousel-center" 19 | ) { 20 | // Grab the elements 21 | this.elem = document.getElementById(sliderID); 22 | if (this.elem == null) return Carousel.InvalidSliderStructure(sliderID); 23 | 24 | this.slider = this.elem.querySelector(`.${sliderClass}`); 25 | if (this.slider == null) return Carousel.InvalidSliderStructure(sliderID); 26 | 27 | this.slides = this.slider.querySelectorAll(`.${slideClass}`); 28 | if (!this.slides.length) return Carousel.InvalidSliderStructure(sliderID); 29 | 30 | this.center = document.getElementById(centerID); 31 | if (this.center == null) return Carousel.InvalidSliderStructure(sliderID); 32 | 33 | this.center.addEventListener("contextmenu", function (event) { 34 | event.preventDefault(); 35 | }); 36 | 37 | // this.slider.style.transition = "transform 0.3s ease"; 38 | 39 | // Initialize properties 40 | this.activeSlide = null; 41 | this.startSlide = null; 42 | this.startX = null; 43 | this.dragStartX = null; 44 | this.moved = false; 45 | this.dragStarted = false; 46 | this.centerClick = false; 47 | this.activeHoldStarted = false; 48 | this.onChange = (activeSlide) => {}; 49 | this.onActiveClick = () => {}; 50 | this.onActiveHoldStart = () => {}; 51 | this.onActiveHoldEnd = () => {}; 52 | 53 | // Add event listeners 54 | this.elem.addEventListener("mousedown", (e) => { 55 | this.onDragStart(e); 56 | }); 57 | this.elem.addEventListener("touchstart", (e) => { 58 | this.onDragStart(e); 59 | }); 60 | this.elem.addEventListener("mouseleave", () => { 61 | if (this.moved) { 62 | this.onDragEnd(); 63 | } 64 | }); 65 | this.elem.addEventListener("mouseup", () => { 66 | if (this.activeHoldStarted) { 67 | this.endHoldTimer(); 68 | } else if (this.moved) { 69 | this.onDragEnd(); 70 | } else { 71 | // this.activeSlide = null; 72 | this.dragStarted = false; 73 | this.elem.classList.remove("active"); 74 | this.slider.style.transition = "transform 0.3s ease"; 75 | 76 | this.cancelHoldTimer(); 77 | 78 | if (this.centerClick) { 79 | this.onActiveClick && this.onActiveClick(); 80 | } 81 | this.centerClick = false; 82 | } 83 | }); 84 | this.elem.addEventListener("touchend", () => { 85 | if (this.activeHoldStarted) { 86 | this.endHoldTimer(); 87 | } else if (this.moved) { 88 | this.onDragEnd(); 89 | } else { 90 | // this.activeSlide = null; 91 | this.dragStarted = false; 92 | this.elem.classList.remove("active"); 93 | this.slider.style.transition = "transform 0.3s ease"; 94 | 95 | this.cancelHoldTimer(); 96 | 97 | // if (this.centerClick) { 98 | // this.onActiveClick && this.onActiveClick(); 99 | // } 100 | this.centerClick = false; 101 | } 102 | }); 103 | this.elem.addEventListener("mousemove", (e) => { 104 | if (this.dragStarted && !this.activeHoldStarted) { 105 | this.onMove(e); 106 | } 107 | }); 108 | this.elem.addEventListener("touchmove", (e) => { 109 | if (this.dragStarted && !this.activeHoldStarted) { 110 | this.onMove(e); 111 | } 112 | }); 113 | window.addEventListener("resize", () => { 114 | this.moveToActiveSlide(); 115 | }); 116 | 117 | // add event listener to all slides to track clicks 118 | this.slides.forEach((slide, index) => { 119 | slide.addEventListener("mouseup", (e) => { 120 | if (!this.moved) { 121 | this.elem.classList.remove("active"); 122 | this.setActiveSlide(index); 123 | this.moveToActiveSlide(); 124 | this.moved = false; 125 | this.dragStarted = false; 126 | this.centerClick = false; 127 | } 128 | }); 129 | }); 130 | 131 | // Set the active slide and classes 132 | this.setActiveSlide(0); 133 | this.moveToActiveSlide(); 134 | } 135 | 136 | /** 137 | * Logs a warning for an invalid slider structure (empty or missing elements) 138 | * 139 | * @param {String} sliderID 140 | */ 141 | static InvalidSliderStructure = (sliderID) => { 142 | console.warn( 143 | `The DraggableSlider with ID ${sliderID} will not work as it has some missing elements.` 144 | ); 145 | }; 146 | 147 | /** 148 | * Get the index of the slide which is closest to the center of the slider 149 | * 150 | * @returns {Number} the index of the slide 151 | */ 152 | getActiveSlide = () => { 153 | let res = 0; 154 | for (let i = 1; i < this.slides.length; i++) 155 | if ( 156 | Math.abs(this.getSlideOffset(i)) < Math.abs(this.getSlideOffset(i - 1)) 157 | ) 158 | res = i; 159 | return res; 160 | }; 161 | 162 | /** 163 | * Get the absolute horizontal offset of a slide from the slider's center point 164 | * 165 | * @param {Number} slide the index of the slide 166 | * @returns {Number} the absolute of the offset in pixels 167 | */ 168 | getSlideOffset = (slide) => { 169 | let sliderRect = this.elem.getBoundingClientRect(); 170 | let slideRect = this.slides[slide].getBoundingClientRect(); 171 | return ( 172 | slideRect.left + 173 | slideRect.width / 2 - 174 | (sliderRect.left + sliderRect.width / 2) 175 | ); 176 | }; 177 | 178 | /** 179 | * Get the left CSS property value of the slider's inner 180 | * @returns {Number} 181 | */ 182 | getInnerLeft = () => { 183 | let res = parseInt(this.slider.style.transform.split("(")[1]); 184 | if (isNaN(res) || res == null) return 0; 185 | return res; 186 | }; 187 | 188 | /** 189 | * Initialize the active state of the slider 190 | * 191 | * @param {Event} e 192 | */ 193 | onDragStart = (e) => { 194 | console.log("drag start"); 195 | const centerClick = e.target.id === "carousel-center"; 196 | 197 | this.slider.style.transition = ""; 198 | 199 | if (centerClick) { 200 | this.centerClick = true; 201 | } 202 | 203 | this.elem.classList.add("active"); 204 | this.moved = false; 205 | this.startX = e.pageX || e.touches[0].pageX; 206 | this.dragStartX = e.pageX || e.touches[0].pageX; 207 | this.startSlide = this.activeSlide; 208 | this.dragStarted = true; 209 | 210 | this.startHoldTimer(); 211 | }; 212 | 213 | /** 214 | * Remove the active state of the slider 215 | */ 216 | onDragEnd = () => { 217 | console.log("drag end"); 218 | this.elem.classList.remove("active"); 219 | this.slider.style.transition = "transform 0.3s ease"; 220 | 221 | this.cancelHoldTimer(); 222 | 223 | this.moveToActiveSlide(); 224 | this.moved = false; 225 | this.dragStarted = false; 226 | this.dragStartX = null; 227 | this.centerClick = false; 228 | }; 229 | 230 | /** 231 | * Get the touch / mouse move distance and move the slider inner 232 | * 233 | * @param {Event} e 234 | */ 235 | onMove = (e) => { 236 | let pos = e.pageX || e.touches?.[0].pageX; 237 | 238 | // console.log("on move"); 239 | // console.log("start of drag:", this.startX); 240 | // console.log("new position:", e.pageX); 241 | // console.log("new position (touch):", e.touches?.[0].pageX); 242 | // console.log("pos:", pos); 243 | 244 | const movedDistance = pos - this.dragStartX; 245 | const moved = movedDistance >= 1 || movedDistance <= -1; 246 | 247 | if (moved) { 248 | this.moved = true; 249 | if (this.holdTimer) { 250 | this.cancelHoldTimer(); 251 | } 252 | } 253 | 254 | if (!this.elem.classList.contains("active")) return; 255 | 256 | e.preventDefault(); 257 | 258 | let dist = pos - this.startX; 259 | 260 | this.startX = pos; 261 | // this.slider.style.left = this.getInnerLeft() + dist + "px"; 262 | this.slider.style.transform = `translate(${this.getInnerLeft() + dist}px)`; 263 | this.setActiveSlide(this.getActiveSlide()); 264 | this.centerClick = false; 265 | }; 266 | 267 | /** 268 | * Move the slider to the active slide 269 | */ 270 | moveToActiveSlide = () => { 271 | if (this.activeSlide == null) this.activeSlide = 0; 272 | this.moveToSlide(this.activeSlide); 273 | }; 274 | 275 | /** 276 | * Move the slider to a specified slide, to the last one if the number is greater than the 277 | * amount of slides or to the first one if the number is inferior to zero. 278 | * 279 | * @param {Number} slide the index of the slide 280 | */ 281 | moveToSlide = (slide) => { 282 | this.slider.style.transform = `translate(${ 283 | this.getInnerLeft() - this.getSlideOffset(this.activeSlide) + "px" 284 | })`; 285 | 286 | setTimeout(() => { 287 | this.onChange && this.onChange(this.activeSlide); 288 | }, 100); 289 | }; 290 | 291 | /** 292 | * Set a slide as active and update the active class 293 | * 294 | * @param {Number} slide the slide to set as active 295 | */ 296 | setActiveSlide = (slide) => { 297 | // Edge cases 298 | if (slide < 0) slide = 0; 299 | if (slide > this.slides.length - 1) slide = this.slides.length - 1; 300 | 301 | this.activeSlide = slide; 302 | for (let i = 0; i < this.slides.length; i++) { 303 | if (i == this.activeSlide) this.slides[i].classList.add("active"); 304 | else this.slides[i].classList.remove("active"); 305 | } 306 | }; 307 | 308 | startHoldTimer = () => { 309 | console.log("startHoldTimer"); 310 | this.holdTimer = setTimeout(() => { 311 | console.log("this.moved", this.moved); 312 | if (!this.moved) { 313 | this.onActiveHoldStart && this.onActiveHoldStart(); 314 | this.activeHoldStarted = true; 315 | } 316 | this.activeHoldStarted = true; 317 | this.holdTimer = null; 318 | }, 500); // Customize the delay before 'onActiveHoldStart' event fires 319 | }; 320 | 321 | endHoldTimer = () => { 322 | console.log("endHoldTimer"); 323 | clearTimeout(this.holdTimer); 324 | this.activeHoldStarted = false; 325 | if (this.onActiveHoldEnd) { 326 | this.onActiveHoldEnd(); 327 | } 328 | }; 329 | 330 | cancelHoldTimer = () => { 331 | console.log("cancelHoldTimer"); 332 | clearTimeout(this.holdTimer); 333 | }; 334 | } 335 | 336 | export default Carousel; 337 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import * as deepar from "deepar"; 2 | import Carousel from "./carousel.js"; 3 | 4 | // Log the version. Just in case. 5 | console.log("Deepar version: " + deepar.version); 6 | 7 | // Top-level await is not supported. 8 | // So we wrap the whole code in an async function that is called immediatly. 9 | (async function () { 10 | // Get the element you want to place DeepAR into. DeepAR will inherit its width and height from this and fill it. 11 | const previewElement = document.getElementById("ar-screen"); 12 | 13 | // trigger loading progress bar animation 14 | const loadingProgressBar = document.getElementById("loading-progress-bar"); 15 | loadingProgressBar.style.width = "100%"; 16 | 17 | // All the effects are in the public/effects folder. 18 | // Here we define the order of effect files. 19 | const effectList = [ 20 | "effects/ray-ban-wayfarer.deepar", 21 | "effects/viking_helmet.deepar", 22 | "effects/MakeupLook.deepar", 23 | "effects/Split_View_Look.deepar", 24 | "effects/flower_face.deepar", 25 | "effects/Stallone.deepar", 26 | "effects/galaxy_background_web.deepar", 27 | "effects/Humanoid.deepar", 28 | "effects/Neon_Devil_Horns.deepar", 29 | "effects/Ping_Pong.deepar", 30 | "effects/Pixel_Hearts.deepar", 31 | "effects/Snail.deepar", 32 | "effects/Hope.deepar", 33 | "effects/Vendetta_Mask.deepar", 34 | "effects/Fire_Effect.deepar", 35 | ]; 36 | 37 | let deepAR = null; 38 | 39 | // Initialize DeepAR with an effect file. 40 | try { 41 | deepAR = await deepar.initialize({ 42 | licenseKey: "your_license_key_goes_here", 43 | previewElement, 44 | effect: effectList[0], 45 | // Removing the rootPath option will make DeepAR load the resources from the JSdelivr CDN, 46 | // which is fine for development but is not recommended for production since it's not optimized for performance and can be unstable. 47 | // More info here: https://docs.deepar.ai/deepar-sdk/platforms/web/tutorials/download-optimizations/#custom-deployment-of-deepar-web-resources 48 | rootPath: "./deepar-resources", 49 | additionalOptions: { 50 | cameraConfig: { 51 | // facingMode: 'environment' // uncomment this line to use the rear camera 52 | }, 53 | }, 54 | }); 55 | } catch (error) { 56 | console.error(error); 57 | document.getElementById("loading-screen").style.display = "none"; 58 | document.getElementById("permission-denied-screen").style.display = "block"; 59 | return; 60 | } 61 | 62 | // Hide the loading screen. 63 | document.getElementById("loading-screen").style.display = "none"; 64 | document.getElementById("ar-screen").style.display = "block"; 65 | 66 | window.effect = effectList[0]; 67 | 68 | const glassesCarousel = new Carousel("carousel"); 69 | glassesCarousel.onChange = async (value) => { 70 | const loadingSpinner = document.getElementById("loading-spinner"); 71 | 72 | if (window.effect !== effectList[value]) { 73 | loadingSpinner.style.display = "block"; 74 | await deepAR.switchEffect(effectList[value]); 75 | window.effect = effectList[value]; 76 | } 77 | loadingSpinner.style.display = "none"; 78 | }; 79 | })(); 80 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | entry: "./src/index.js", 5 | output: { 6 | filename: "main.js", 7 | path: path.resolve(__dirname, "dist"), 8 | clean: true, 9 | }, 10 | target: "web", 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.(wasm)|(bin)|(obj)$/i, 15 | include: [ 16 | path.resolve(__dirname, 'node_modules/deepar/'), 17 | ], 18 | type: 'asset/resource', 19 | }, 20 | { 21 | include: [ 22 | path.resolve(__dirname, 'effects/'), 23 | ], 24 | type: 'asset/resource', 25 | }, 26 | ], 27 | }, 28 | resolve: { 29 | alias: { 30 | '@effects': path.resolve(__dirname, 'effects/'), 31 | }, 32 | }, 33 | performance: { 34 | maxEntrypointSize: 1000000, 35 | maxAssetSize: 10000000, 36 | }, 37 | devServer: { 38 | static: [ 39 | { 40 | directory: path.join(__dirname, "public"), 41 | }, 42 | { 43 | directory: path.join(__dirname, "node_modules/deepar"), 44 | publicPath: "/deepar-resources", 45 | }, 46 | ], 47 | compress: true, 48 | port: 9000, 49 | }, 50 | }; 51 | --------------------------------------------------------------------------------