├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── dist ├── bfi.js └── bfi.min.js ├── index.html ├── package-lock.json ├── package.json └── src └── css ├── bfi.css └── bfi.min.css /.gitattributes: -------------------------------------------------------------------------------- 1 | index.html linguist-documentation 2 | dist/* linguist-vendored=false 3 | src/** linguist-vendored=false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # parcel-bundler cache (https://parceljs.org/) 61 | .cache 62 | 63 | # next.js build output 64 | .next 65 | 66 | # nuxt.js build output 67 | .nuxt 68 | 69 | # vuepress build output 70 | .vuepress/dist 71 | 72 | # Serverless directories 73 | .serverless -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 nifte | Michael Lombardo 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 | # Better File Input 2 | A lightweight vanilla JavaScript library that converts HTML File Inputs into user-friendly, interactive elements with minimal setup required 3 | 4 | [![GitHub](https://img.shields.io/github/license/nifte/better-file-input.svg)](https://github.com/nifte/better-file-input/blob/master/LICENSE) 5 | [![npm](https://img.shields.io/npm/v/better-file-input.svg)](https://www.npmjs.com/package/better-file-input) 6 | 7 | ## Demo 8 | View the live demo [here](https://nifte.github.io/better-file-input) 9 | 10 | ## Installation 11 | ### CDN 12 | Add the following to the `` of your document: 13 | ```html 14 | 15 | ``` 16 | 17 | ### npm 18 | 1. Install better-file-input with the following command: 19 | ``` 20 | npm i better-file-input 21 | ``` 22 | 2. Add the following to the `` of your document: 23 | ```html 24 | 25 | ``` 26 | 27 | ### Manual 28 | 1. Download `dist/bfi.js` (or `dist/bfi.min.js`) 29 | 2. Add the following to the `` of your document: 30 | ```html 31 | 32 | ``` 33 | 34 | ## Usage 35 | Simply add `class="bfi"` to your file inputs to automatically convert them to *better* file inputs: 36 | ```html 37 | 38 | ``` 39 | **Note:** You can also add the `multiple` and `disabled` attributes to your better file inputs 40 | 41 | Dynamically-created file inputs will **not** be automatically converted - you need to call `bfi_init()` after creation to convert them: 42 | ```javascript 43 | bfi_init() 44 | ``` 45 | 46 | You can call `bfi_clear()` to programmatically remove files from a converted file input: 47 | ```javascript 48 | bfi_clear() // Clear all better file inputs 49 | bfi_clear('#myFileInput') // Clear the better file input with the id 'myFileInput' 50 | ``` 51 | 52 | ## Customization 53 | The `bfi_init()` function accepts one optional argument - an object containing pre-defined options to customize the look of your better file inputs: 54 | ```javascript 55 | bfi_init({ 56 | 'containerColor': '#b8bfd8', // The color of the file container 57 | 'labelColor': 'rgb(77, 79, 86)', // The color of the file container label 58 | 'fileColor': 'linear-gradient(#84f189, #53b658)', // The color of the files 59 | 'fileNameColor': 'darkblue', // The color of the file names 60 | 'fileInfoColor': 'rgba(55, 55, 55, 0.75)', // The color of the file size info 61 | 'dragDropBorder': '3px dotted #374f6d' // The drag & drop border 62 | }) 63 | ``` 64 | To reset the look of your better file inputs, simply call the `bfi_reset()` function: 65 | ```javascript 66 | bfi_reset() 67 | ``` -------------------------------------------------------------------------------- /dist/bfi.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Better File Input 1.3.0 (https://github.com/nifte/better-file-input) 3 | * by nifte (https://github.com/nifte) 4 | * Licensed under MIT (https://github.com/nifte/better-file-input/blob/master/LICENSE) 5 | */ 6 | 7 | // Define styles 8 | const style = '@-webkit-keyframes file_grow{0%{max-height:0;padding:0 10px}100%{max-height:100px}}@keyframes file_grow{0%{max-height:0;padding:0 10px}100%{max-height:100px}}@-webkit-keyframes shadow_grow{0%{-webkit-transform:scale(0);transform:scale(0)}100%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes shadow_grow{0%{-webkit-transform:scale(0);transform:scale(0)}100%{-webkit-transform:scale(1);transform:scale(1)}}.bfi-container{display:block;position:relative;width:100%;height:unset;margin:0;padding:0;border-radius:5px;background:#f0f0f0;-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-transition:max-height 1s ease;transition:max-height 1s ease}.bfi-container.expanded{border:4px dashed gray}.bfi-container *{-webkit-box-sizing:border-box;box-sizing:border-box}.bfi-converted,.bfi-converted-multi{opacity:0;position:absolute;top:0;left:0;width:100%;height:100%}.bfi-container:not(.expanded) .bfi-converted,.bfi-container:not(.expanded) .bfi-converted-multi{z-index:-10}.bfi-container.expanded .bfi-converted,.bfi-container.expanded .bfi-converted-multi{z-index:20}.bfi-label,.bfi-label-selected{display:inline-block;width:100%;height:unset;margin:0;text-align:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;z-index:10}.bfi-container:not(.expanded) .bfi-label,.bfi-label-selected{padding:10px 20px}.bfi-container.expanded .bfi-label{padding:40px 20px}.bfi-label{-webkit-transition:padding .25s ease;transition:padding .25s ease}.bfi-clear,.bfi-label span{cursor:pointer;text-decoration:underline}.bfi-file{display:inline-block;width:-o-calc(100% - 20px);width:calc(100% - 20px);padding:6px 10px;background:#646464;background:-webkit-gradient(linear,left bottom,left top,from(rgba(90,90,90,1)),color-stop(75%,rgba(110,110,110,1)));background:linear-gradient(0deg,rgba(90,90,90,1) 0,rgba(110,110,110,1) 75%);color:#fff;border-radius:7px;z-index:10;line-height:1em;-webkit-animation:file_grow .7s ease;animation:file_grow .7s ease}.bfi-converted~.bfi-file{margin:10px}.bfi-converted-multi~.bfi-file{margin:0 10px 10px 10px}.bfi-file i{font-style:normal;font-size:.8em;color:#b4b4b4}.bfi-file .bfi-clear{position:absolute;right:25px;top:calc(50% - 2px);-webkit-transform:translateY(-50%);transform:translateY(-50%);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.bfi-shadow-container{position:absolute;display:none;margin:0;padding:0;left:0;right:0;top:0;bottom:0;clip:rect(0,auto,auto,0);z-index:15}.bfi-container.expanded .bfi-shadow-container{display:unset}.bfi-shadow{position:absolute;display:none;width:350px;height:350px;border-radius:50%;background:rgba(0,0,0,.06);-webkit-transition:left .1s ease,top .1s ease;transition:left .1s ease,top .1s ease}.bfi-container.hovering .bfi-shadow{display:unset;-webkit-animation:shadow_grow .5s ease;animation:shadow_grow .5s ease}'; 9 | 10 | // Initialize function 11 | var bfi_counter = 0; 12 | function bfi_init(options = null) { 13 | if (document.querySelectorAll('.bfi-style').length < 1) document.body.insertAdjacentHTML('beforeend', ``); 14 | let bfi = document.querySelectorAll('input[type="file"].bfi'); 15 | let total = bfi.length; 16 | for (let i = 0; i < total; i++) { 17 | bfi_counter++; 18 | let input = bfi[i]; 19 | let container = document.createElement('div'); 20 | let label = ''; 21 | input.parentElement.insertBefore(container, input); 22 | container.appendChild(input); 23 | container.classList.add('bfi-container'); 24 | if (input.hasAttribute('multiple')) { 25 | input.classList.add('bfi-converted-multi'); 26 | container.insertAdjacentHTML('beforeend', ''); 27 | label = 'Drag & Drop files here, or Browse'; 28 | } else { 29 | input.classList.add('bfi-converted'); 30 | label = 'Drag & Drop file here, or Browse'; 31 | } 32 | if (!input.hasAttribute('id')) input.setAttribute('id', `bfi-${bfi_counter}`); 33 | let id = input.getAttribute('id'); 34 | container.insertAdjacentHTML('afterbegin', ``); 35 | container.insertAdjacentHTML('beforeend', '
'); 36 | input.setAttribute('tabindex', -1); 37 | } 38 | document.querySelectorAll('input[type="file"].bfi').forEach(el => { el.classList.remove('bfi'); }); 39 | if (options != null) { 40 | let style_override = ''; 41 | if (options.hasOwnProperty('labelColor')) style_override += `.bfi-label, .bfi-label-selected { color: ${options.labelColor} }`; 42 | if (options.hasOwnProperty('containerColor')) style_override += `.bfi-container { background: ${options.containerColor} }`; 43 | if (options.hasOwnProperty('fileColor')) style_override += `.bfi-file { background: ${options.fileColor} }`; 44 | if (options.hasOwnProperty('fileNameColor')) style_override += `.bfi-file { color: ${options.fileNameColor} }`; 45 | if (options.hasOwnProperty('fileInfoColor')) style_override += `.bfi-file i { color: ${options.fileInfoColor} }`; 46 | if (options.hasOwnProperty('dragDropBorder')) style_override += `.bfi-container.expanded { border: ${options.dragDropBorder} }`; 47 | document.body.insertAdjacentHTML('beforeend', ``); 48 | } 49 | } 50 | 51 | // Reset style overrides 52 | function bfi_reset() { 53 | let styles = document.querySelectorAll('.bfi-style-override'); 54 | if (styles.length) { 55 | styles.forEach(el => { 56 | el.remove(); 57 | }); 58 | } 59 | } 60 | 61 | // Initialize on DOM load 62 | document.addEventListener('DOMContentLoaded', () => { 63 | bfi_init(); 64 | }); 65 | 66 | // Drag files onto page 67 | var bfi_drag_timeout, bfi_hover_timeout; 68 | window.addEventListener('dragover', e => { 69 | let dt = e.dataTransfer; 70 | if (dt.types && (dt.types.indexOf ? dt.types.indexOf('Files') != -1 : dt.types.contains('Files'))) { 71 | document.querySelectorAll('.bfi-container').forEach(el => { 72 | if (el.querySelector('.bfi-label').style.display != 'none') el.classList.add('expanded'); 73 | }); 74 | clearTimeout(bfi_drag_timeout); 75 | bfi_drag_timeout = setTimeout(() => { 76 | document.querySelectorAll('.bfi-container').forEach(el => { el.classList.remove('expanded'); }); 77 | }, 100); 78 | document.querySelectorAll('.bfi-shadow').forEach(el => { 79 | let container = el.closest('.bfi-shadow-container').getBoundingClientRect(); 80 | let size = Number(container.width * 0.75).toFixed(); 81 | if (size > 500) size = 500; 82 | el.style.width = size + 'px'; 83 | el.style.height = size + 'px'; 84 | el.style.left = Number(e.pageX - container.left - (size / 2)).toFixed() + 'px'; 85 | el.style.top = Number(e.pageY - container.top - (size / 2)).toFixed() + 'px'; 86 | }); 87 | if (e.target.classList.contains('bfi-converted') || e.target.classList.contains('bfi-converted-multi')) { 88 | let container = e.target.closest('.bfi-container'); 89 | container.classList.add('hovering'); 90 | clearTimeout(bfi_hover_timeout); 91 | bfi_hover_timeout = setTimeout(() => { 92 | container.classList.remove('hovering'); 93 | }, 100); 94 | } 95 | } 96 | }); 97 | 98 | // Drag files out of container 99 | window.addEventListener('dragleave', e => { 100 | if (e.target.classList.contains('bfi-converted') || e.target.classList.contains('bfi-converted-multi')) { 101 | let container = e.target.closest('.bfi-container'); 102 | container.classList.remove('hovering'); 103 | } 104 | }); 105 | 106 | // Prevent browser from opening any dragged files 107 | window.addEventListener('dragover', e => { 108 | if (!e.target.classList.contains('bfi-converted') && !e.target.classList.contains('bfi-converted-multi')) { 109 | e.preventDefault(); 110 | e.dataTransfer.effectAllowed = 'none'; 111 | e.dataTransfer.dropEffect = 'none'; 112 | } 113 | }); 114 | 115 | // Prevent browser from opening any dropped files 116 | window.addEventListener('drop', e => { 117 | if (!e.target.classList.contains('bfi-converted') && !e.target.classList.contains('bfi-converted-multi')) { 118 | e.preventDefault(); 119 | e.dataTransfer.effectAllowed = 'none'; 120 | e.dataTransfer.dropEffect = 'none'; 121 | } 122 | }); 123 | 124 | // Watch for file updates 125 | document.addEventListener('change', e => { 126 | if (e.target.classList.contains('bfi-converted')) { 127 | let container = e.target.closest('.bfi-container'); 128 | if (e.target.files.length) { 129 | container.querySelector('.bfi-label').style.display = 'none'; 130 | container.querySelectorAll('.bfi-file').forEach(el => { el.remove(); }); 131 | let file = e.target.files[0].name; 132 | let size = Number(e.target.files[0].size / 1000).toFixed(1) + ' KB'; 133 | container.insertAdjacentHTML('beforeend', `
Undo${file}
${size}
`); 134 | } else { 135 | container.querySelector('.bfi-label').style.display = ''; 136 | container.querySelectorAll('.bfi-file').forEach(el => { el.remove(); }); 137 | } 138 | } 139 | if (e.target.classList.contains('bfi-converted-multi')) { 140 | let container = e.target.closest('.bfi-container'); 141 | if (e.target.files.length) { 142 | container.querySelector('.bfi-label').style.display = 'none'; 143 | container.querySelector('.bfi-label-selected').style.display = ''; 144 | container.querySelectorAll('.bfi-file').forEach(el => { el.remove(); }); 145 | let files = []; 146 | for (let i = 0; i < e.target.files.length; i++) { 147 | files.push({ 148 | 'name': e.target.files[i].name, 149 | 'size': Number(e.target.files[i].size / 1000).toFixed(1) + ' KB' 150 | }); 151 | } 152 | let fileCount = '1 file'; 153 | if (files.length > 1) fileCount = `${files.length} files`; 154 | container.querySelector('.bfi-label-selected').innerHTML = `${fileCount} selected. Undo`; 155 | files.forEach(file => { 156 | container.insertAdjacentHTML('beforeend', `
${file.name}
${file.size}
`); 157 | }); 158 | } else { 159 | container.querySelector('.bfi-label').style.display = ''; 160 | container.querySelector('.bfi-label-selected').style.display = 'none'; 161 | container.querySelectorAll('.bfi-file').forEach(el => { el.remove(); }); 162 | } 163 | } 164 | }); 165 | 166 | // Simulate click on focused bfi element 167 | document.addEventListener('keyup', e => { 168 | if (e.keyCode == 32 || e.keyCode == 13) { 169 | if (document.activeElement.classList.contains('bfi-label')) document.activeElement.click(); 170 | if (document.activeElement.classList.contains('bfi-clear')) document.activeElement.click(); 171 | } 172 | }); 173 | 174 | // Clear files on undo 175 | document.addEventListener('click', e => { 176 | if (e.target.classList.contains('bfi-clear')) { 177 | let container = e.target.closest('.bfi-container'); 178 | let inputID = container.querySelector('.bfi-converted, .bfi-converted-multi').getAttribute('id'); 179 | bfi_clear(`#${inputID}`); 180 | } 181 | }); 182 | 183 | // Clear files from a bfi element 184 | function bfi_clear(query = null) { 185 | if (query == null) query = '.bfi-converted, .bfi-converted-multi'; 186 | let inputs = document.querySelectorAll(query); 187 | if (inputs.length) { 188 | inputs.forEach(el => { 189 | el.value = ''; 190 | el.dispatchEvent(new Event('change', { 'bubbles': true })); 191 | }); 192 | } 193 | } -------------------------------------------------------------------------------- /dist/bfi.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Better File Input 1.3.0 (https://github.com/nifte/better-file-input) 3 | * by nifte (https://github.com/nifte) 4 | * Licensed under MIT (https://github.com/nifte/better-file-input/blob/master/LICENSE) 5 | */ 6 | const style="@-webkit-keyframes file_grow{0%{max-height:0;padding:0 10px}100%{max-height:100px}}@keyframes file_grow{0%{max-height:0;padding:0 10px}100%{max-height:100px}}@-webkit-keyframes shadow_grow{0%{-webkit-transform:scale(0);transform:scale(0)}100%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes shadow_grow{0%{-webkit-transform:scale(0);transform:scale(0)}100%{-webkit-transform:scale(1);transform:scale(1)}}.bfi-container{display:block;position:relative;width:100%;height:unset;margin:0;padding:0;border-radius:5px;background:#f0f0f0;-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-transition:max-height 1s ease;transition:max-height 1s ease}.bfi-container.expanded{border:4px dashed gray}.bfi-container *{-webkit-box-sizing:border-box;box-sizing:border-box}.bfi-converted,.bfi-converted-multi{opacity:0;position:absolute;top:0;left:0;width:100%;height:100%}.bfi-container:not(.expanded) .bfi-converted,.bfi-container:not(.expanded) .bfi-converted-multi{z-index:-10}.bfi-container.expanded .bfi-converted,.bfi-container.expanded .bfi-converted-multi{z-index:20}.bfi-label,.bfi-label-selected{display:inline-block;width:100%;height:unset;margin:0;text-align:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;z-index:10}.bfi-container:not(.expanded) .bfi-label,.bfi-label-selected{padding:10px 20px}.bfi-container.expanded .bfi-label{padding:40px 20px}.bfi-label{-webkit-transition:padding .25s ease;transition:padding .25s ease}.bfi-clear,.bfi-label span{cursor:pointer;text-decoration:underline}.bfi-file{display:inline-block;width:-o-calc(100% - 20px);width:calc(100% - 20px);padding:6px 10px;background:#646464;background:-webkit-gradient(linear,left bottom,left top,from(rgba(90,90,90,1)),color-stop(75%,rgba(110,110,110,1)));background:linear-gradient(0deg,rgba(90,90,90,1) 0,rgba(110,110,110,1) 75%);color:#fff;border-radius:7px;z-index:10;line-height:1em;-webkit-animation:file_grow .7s ease;animation:file_grow .7s ease}.bfi-converted~.bfi-file{margin:10px}.bfi-converted-multi~.bfi-file{margin:0 10px 10px 10px}.bfi-file i{font-style:normal;font-size:.8em;color:#b4b4b4}.bfi-file .bfi-clear{position:absolute;right:25px;top:calc(50% - 2px);-webkit-transform:translateY(-50%);transform:translateY(-50%);-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.bfi-shadow-container{position:absolute;display:none;margin:0;padding:0;left:0;right:0;top:0;bottom:0;clip:rect(0,auto,auto,0);z-index:15}.bfi-container.expanded .bfi-shadow-container{display:unset}.bfi-shadow{position:absolute;display:none;width:350px;height:350px;border-radius:50%;background:rgba(0,0,0,.06);-webkit-transition:left .1s ease,top .1s ease;transition:left .1s ease,top .1s ease}.bfi-container.hovering .bfi-shadow{display:unset;-webkit-animation:shadow_grow .5s ease;animation:shadow_grow .5s ease}";var bfi_drag_timeout,bfi_hover_timeout,bfi_counter=0;function bfi_init(e=null){document.querySelectorAll(".bfi-style").length<1&&document.body.insertAdjacentHTML("beforeend",``);let t=document.querySelectorAll('input[type="file"].bfi'),i=t.length;for(let e=0;e