├── README.md ├── app.js ├── copy-sound.wav ├── index.html ├── screenshot1.png ├── screenshot2.png └── style.css /README.md: -------------------------------------------------------------------------------- 1 | # WB Color Picker 2 | 3 | 100% vanilla JavaScript code to design and functioning this cute little color picker application. No third party code has been used, well structured and easy to understand code syntax. This could be one of the great project to understand JavaScript and DOM workflow together. 4 | 5 | [Live Demo](https://mrhm-dev.github.io/wb-color-picker/) 6 | 7 |  8 | 9 |  10 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Date: 16-12-2021 3 | * Author: HM Nayem 4 | * Description: Color picker application with huge dom functionalities 5 | */ 6 | 7 | // Globals 8 | let toastContainer = null; 9 | const defaultColor = { 10 | red: 221, 11 | green: 222, 12 | blue: 238, 13 | }; 14 | const defaultPresetColors = [ 15 | '#ffcdd2', 16 | '#f8bbd0', 17 | '#e1bee7', 18 | '#ff8a80', 19 | '#ff80ab', 20 | '#ea80fc', 21 | '#b39ddb', 22 | '#9fa8da', 23 | '#90caf9', 24 | '#b388ff', 25 | '#8c9eff', 26 | '#82b1ff', 27 | '#03a9f4', 28 | '#00bcd4', 29 | '#009688', 30 | '#80d8ff', 31 | '#84ffff', 32 | '#a7ffeb', 33 | '#c8e6c9', 34 | '#dcedc8', 35 | '#f0f4c3', 36 | '#b9f6ca', 37 | '#ccff90', 38 | '#ffcc80', 39 | ]; 40 | let customColors = new Array(24); 41 | const copySound = new Audio('./copy-sound.wav'); 42 | 43 | // onload handler 44 | window.onload = () => { 45 | main(); 46 | updateColorCodeToDom(defaultColor); 47 | // display preset colors 48 | displayColorBoxes( 49 | document.getElementById('preset-colors'), 50 | defaultPresetColors 51 | ); 52 | const customColorsString = localStorage.getItem('custom-colors'); 53 | if (customColorsString) { 54 | customColors = JSON.parse(customColorsString); 55 | displayColorBoxes( 56 | document.getElementById('custom-colors'), 57 | customColors 58 | ); 59 | } 60 | }; 61 | 62 | // main or boot function, this function will take care of getting all the DOM references 63 | function main() { 64 | // dom references 65 | const generateRandomColorBtn = document.getElementById( 66 | 'generate-random-color' 67 | ); 68 | const colorModeHexInp = document.getElementById('input-hex'); 69 | const colorSliderRed = document.getElementById('color-slider-red'); 70 | const colorSliderGreen = document.getElementById('color-slider-green'); 71 | const colorSliderBlue = document.getElementById('color-slider-blue'); 72 | const copyToClipboardBtn = document.getElementById('copy-to-clipboard'); 73 | const saveToCustomBtn = document.getElementById('save-to-custom'); 74 | const presetColorsParent = document.getElementById('preset-colors'); 75 | const customColorsParent = document.getElementById('custom-colors'); 76 | const bgPreview = document.getElementById('bg-preview'); 77 | const bgFileInput = document.getElementById('bg-file-input'); 78 | const bgFileInputBtn = document.getElementById('bg-file-input-btn'); 79 | const bgFileDeleteBtn = document.getElementById('bg-file-delete-btn'); 80 | bgFileDeleteBtn.style.display = 'none'; 81 | const bgController = document.getElementById('bg-controller'); 82 | bgController.style.display = 'none'; 83 | 84 | // event listeners 85 | generateRandomColorBtn.addEventListener( 86 | 'click', 87 | handleGenerateRandomColorBtn 88 | ); 89 | colorModeHexInp.addEventListener('keyup', handleColorModeHexInp); 90 | colorSliderRed.addEventListener( 91 | 'change', 92 | handleColorSliders(colorSliderRed, colorSliderGreen, colorSliderBlue) 93 | ); 94 | colorSliderGreen.addEventListener( 95 | 'change', 96 | handleColorSliders(colorSliderRed, colorSliderGreen, colorSliderBlue) 97 | ); 98 | colorSliderBlue.addEventListener( 99 | 'change', 100 | handleColorSliders(colorSliderRed, colorSliderGreen, colorSliderBlue) 101 | ); 102 | copyToClipboardBtn.addEventListener('click', handleCopyToClipboard); 103 | presetColorsParent.addEventListener('click', handlePresetColorsParent); 104 | saveToCustomBtn.addEventListener( 105 | 'click', 106 | handleSaveToCustomBtn(customColorsParent, colorModeHexInp) 107 | ); 108 | 109 | bgFileInputBtn.addEventListener('click', function () { 110 | bgFileInput.click(); 111 | }); 112 | 113 | bgFileInput.addEventListener( 114 | 'change', 115 | handleBgFileInput(bgPreview, bgFileDeleteBtn, bgController) 116 | ); 117 | 118 | bgFileDeleteBtn.addEventListener( 119 | 'click', 120 | handleBgFileDeleteBtn( 121 | bgPreview, 122 | bgFileDeleteBtn, 123 | bgController, 124 | bgFileInput 125 | ) 126 | ); 127 | 128 | document 129 | .getElementById('bg-size') 130 | .addEventListener('change', changeBackgroundPreferences); 131 | document 132 | .getElementById('bg-repeat') 133 | .addEventListener('change', changeBackgroundPreferences); 134 | document 135 | .getElementById('bg-position') 136 | .addEventListener('change', changeBackgroundPreferences); 137 | document 138 | .getElementById('bg-attachment') 139 | .addEventListener('change', changeBackgroundPreferences); 140 | } 141 | 142 | // event handlers 143 | function handleGenerateRandomColorBtn() { 144 | const color = generateColorDecimal(); 145 | updateColorCodeToDom(color); 146 | } 147 | 148 | function handleColorModeHexInp(e) { 149 | const hexColor = e.target.value; 150 | if (hexColor) { 151 | this.value = hexColor.toUpperCase(); 152 | if (isValidHex(hexColor)) { 153 | const color = hexToDecimalColors(hexColor); 154 | updateColorCodeToDom(color); 155 | } 156 | } 157 | } 158 | 159 | function handleColorSliders(colorSliderRed, colorSliderGreen, colorSliderBlue) { 160 | return function () { 161 | const color = { 162 | red: parseInt(colorSliderRed.value), 163 | green: parseInt(colorSliderGreen.value), 164 | blue: parseInt(colorSliderBlue.value), 165 | }; 166 | updateColorCodeToDom(color); 167 | }; 168 | } 169 | 170 | function handleCopyToClipboard() { 171 | const colorModeRadios = document.getElementsByName('color-mode'); 172 | const mode = getCheckedValueFromRadios(colorModeRadios); 173 | if (mode === null) { 174 | throw new Error('Invalid Radio Input'); 175 | } 176 | 177 | if (toastContainer !== null) { 178 | toastContainer.remove(); 179 | toastContainer = null; 180 | } 181 | 182 | if (mode === 'hex') { 183 | const hexColor = document.getElementById('input-hex').value; 184 | if (hexColor && isValidHex(hexColor)) { 185 | navigator.clipboard.writeText(`#${hexColor}`); 186 | generateToastMessage(`#${hexColor} Copied`); 187 | } else { 188 | alert('Invalid Hex Code'); 189 | } 190 | } else { 191 | const rgbColor = document.getElementById('input-rgb').value; 192 | if (rgbColor) { 193 | navigator.clipboard.writeText(rgbColor); 194 | generateToastMessage(`${rgbColor} Copied`); 195 | } else { 196 | alert('Invalid RGB Color'); 197 | } 198 | } 199 | } 200 | 201 | function handlePresetColorsParent(event) { 202 | const child = event.target; 203 | if (child.className === 'color-box') { 204 | navigator.clipboard.writeText(child.getAttribute('data-color')); 205 | copySound.volume = 0.2; 206 | copySound.play(); 207 | } 208 | } 209 | 210 | function handleSaveToCustomBtn(customColorsParent, inputHex) { 211 | return function () { 212 | const color = `#${inputHex.value}`; 213 | if (customColors.includes(color)) { 214 | alert('Already Saved'); 215 | return; 216 | } 217 | customColors.unshift(color); 218 | if (customColors.length > 24) { 219 | customColors = customColors.slice(0, 24); 220 | } 221 | localStorage.setItem('custom-colors', JSON.stringify(customColors)); 222 | removeChildren(customColorsParent); 223 | displayColorBoxes(customColorsParent, customColors); 224 | }; 225 | } 226 | 227 | function handleBgFileInput(bgPreview, bgFileDeleteBtn, bgController) { 228 | return function (event) { 229 | const file = event.target.files[0]; 230 | const imgUrl = URL.createObjectURL(file); 231 | bgPreview.style.background = `url(${imgUrl})`; 232 | document.body.style.background = `url(${imgUrl})`; 233 | bgFileDeleteBtn.style.display = 'inline'; 234 | bgController.style.display = 'block'; 235 | }; 236 | } 237 | 238 | function handleBgFileDeleteBtn( 239 | bgPreview, 240 | bgFileDeleteBtn, 241 | bgController, 242 | bgFileInput 243 | ) { 244 | return function () { 245 | bgPreview.style.background = 'none'; 246 | bgPreview.style.backgroundColor = '#dddeee'; 247 | document.body.style.background = 'none'; 248 | document.body.style.backgroundColor = '#dddeee'; 249 | bgFileDeleteBtn.style.display = 'none'; 250 | bgController.style.display = 'none'; 251 | bgFileInput.value = null; 252 | }; 253 | } 254 | 255 | // DOM functions 256 | /** 257 | * Generate a dynamic DOM element to show a toast message 258 | * @param {string} msg 259 | */ 260 | function generateToastMessage(msg) { 261 | toastContainer = document.createElement('div'); 262 | toastContainer.innerText = msg; 263 | toastContainer.className = 'toast-message toast-message-slide-in'; 264 | 265 | toastContainer.addEventListener('click', function () { 266 | toastContainer.classList.remove('toast-message-slide-in'); 267 | toastContainer.classList.add('toast-message-slide-out'); 268 | 269 | toastContainer.addEventListener('animationend', function () { 270 | toastContainer.remove(); 271 | toastContainer = null; 272 | }); 273 | }); 274 | 275 | document.body.appendChild(toastContainer); 276 | } 277 | 278 | /** 279 | * find the checked elements from a list of radio buttons 280 | * @param {Array} nodes 281 | * @returns {string | null} 282 | */ 283 | function getCheckedValueFromRadios(nodes) { 284 | let checkedValue = null; 285 | for (let i = 0; i < nodes.length; i++) { 286 | if (nodes[i].checked) { 287 | checkedValue = nodes[i].value; 288 | break; 289 | } 290 | } 291 | return checkedValue; 292 | } 293 | 294 | /** 295 | * update dom elements with calculated color values 296 | * @param {object} color : ; 297 | */ 298 | function updateColorCodeToDom(color) { 299 | const hexColor = generateHexColor(color); 300 | const rgbColor = generateRGBColor(color); 301 | 302 | document.getElementById( 303 | 'color-display' 304 | ).style.backgroundColor = `#${hexColor}`; 305 | document.getElementById('input-hex').value = hexColor; 306 | document.getElementById('input-rgb').value = rgbColor; 307 | document.getElementById('color-slider-red').value = color.red; 308 | document.getElementById('color-slider-red-label').innerText = color.red; 309 | document.getElementById('color-slider-green').value = color.green; 310 | document.getElementById('color-slider-green-label').innerText = color.green; 311 | document.getElementById('color-slider-blue').value = color.blue; 312 | document.getElementById('color-slider-blue-label').innerText = color.blue; 313 | } 314 | 315 | /** 316 | * create a div element with class name color-box 317 | * @param {string} color : ; 318 | * @returns {object} 319 | */ 320 | function generateColorBox(color) { 321 | const div = document.createElement('div'); 322 | div.className = 'color-box'; 323 | div.style.backgroundColor = color; 324 | div.setAttribute('data-color', color); 325 | 326 | return div; 327 | } 328 | 329 | /** 330 | * this function will create and append new color boxes to it's parent 331 | * @param {object} parent 332 | * @param {Array} colors 333 | */ 334 | function displayColorBoxes(parent, colors) { 335 | colors.forEach((color) => { 336 | if (color && isValidHex(color.slice(1))) { 337 | const colorBox = generateColorBox(color); 338 | parent.appendChild(colorBox); 339 | } 340 | }); 341 | } 342 | 343 | /** 344 | * remove all children from parent 345 | * @param {object} parent 346 | */ 347 | function removeChildren(parent) { 348 | let child = parent.lastElementChild; 349 | while (child) { 350 | parent.removeChild(child); 351 | child = parent.lastElementChild; 352 | } 353 | } 354 | 355 | function changeBackgroundPreferences() { 356 | document.body.style.backgroundSize = 357 | document.getElementById('bg-size').value; 358 | document.body.style.backgroundRepeat = 359 | document.getElementById('bg-repeat').value; 360 | document.body.style.backgroundPosition = 361 | document.getElementById('bg-position').value; 362 | document.body.style.backgroundAttachment = 363 | document.getElementById('bg-attachment').value; 364 | } 365 | 366 | // Utils 367 | 368 | /** 369 | * generate and return an object of three color decimal values 370 | * @returns {object}} 371 | */ 372 | function generateColorDecimal() { 373 | const red = Math.floor(Math.random() * 255); 374 | const green = Math.floor(Math.random() * 255); 375 | const blue = Math.floor(Math.random() * 255); 376 | 377 | return { 378 | red, 379 | green, 380 | blue, 381 | }; 382 | } 383 | 384 | /** 385 | * take a color object of three decimal values and return a hexadecimal color code 386 | * @param {object} color 387 | * @returns {string} 388 | */ 389 | function generateHexColor({ red, green, blue }) { 390 | const getTwoCode = (value) => { 391 | const hex = value.toString(16); 392 | return hex.length === 1 ? `0${hex}` : hex; 393 | }; 394 | 395 | return `${getTwoCode(red)}${getTwoCode(green)}${getTwoCode( 396 | blue 397 | )}`.toUpperCase(); 398 | } 399 | 400 | /** 401 | * take a color object of three decimal values and return a rgb color code 402 | * @param {object} color 403 | * @returns {string} 404 | */ 405 | function generateRGBColor({ red, green, blue }) { 406 | return `rgb(${red}, ${green}, ${blue})`; 407 | } 408 | 409 | /** 410 | * convert hex color to decimal colors object 411 | * @param {string} hex 412 | * @returns {object} 413 | */ 414 | function hexToDecimalColors(hex) { 415 | const red = parseInt(hex.slice(0, 2), 16); 416 | const green = parseInt(hex.slice(2, 4), 16); 417 | const blue = parseInt(hex.slice(4), 16); 418 | 419 | return { 420 | red, 421 | green, 422 | blue, 423 | }; 424 | } 425 | 426 | /** 427 | * validate hex color code 428 | * @param {string} color; 429 | * @returns {boolean} 430 | */ 431 | function isValidHex(color) { 432 | if (color.length !== 6) return false; 433 | return /^[0-9A-Fa-f]{6}$/i.test(color); 434 | } 435 | -------------------------------------------------------------------------------- /copy-sound.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrhm-dev/wb-color-picker/a845f6cdb8204f66bdaf4b8b3eea3d3955522274/copy-sound.wav -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 |Select Copy Mode
50 |Background Preview
170 | 171 |