Last active
March 11, 2025 17:43
-
-
Save ajmas/5710ac15c48b74b806bee9c2073b44dc to your computer and use it in GitHub Desktop.
Basic HTML Drag and drop for files
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.dropzone.dropzone-active { | |
box-shadow: 0px 0px 15px 0px rgba(73, 245, 54, 0.75); | |
} | |
.dropzone.dropzone-badtype { | |
cursor: no-drop; | |
box-shadow: 0px 0px 15px 0px rgba(248, 19, 19, 0.75); | |
content: url("./white-x-icon.svg"); | |
} | |
#error_message { | |
color: red; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
This is a rudementary implementation to allow drag an drop upload on an HTML page. It | |
was made for a project I was working on, but thought I'd share it for anyone interested | |
and for eventual feedback. | |
- Supports limiting: | |
- file type | |
- file size | |
- number of files uploaded | |
- Disables dropping files elsewhere on the page | |
- What you need to provide: | |
- an element to act as drop zone and adjust vlaue of `dropZoneSelector` | |
- file input element and ajdust the value of `fileInputSelector` | |
- error element and adjust the value of `errorSelector` | |
Note, due to browser security model, we can't get the file size during dragging. It is only available | |
once the files are dropped. | |
*/ | |
const supportedFileTypes = ['image/jpeg', 'image/png', 'video/mp4', 'application/zip']; | |
const maxFileCount = 7; | |
const maxFileSizeMB = 24; | |
const dropZoneSelector = '.dropzone'; | |
const fileInputSelector = '#id_file'; | |
const errorSelector = '#error_message'; | |
function toBytes (sizeInMegaBytes) { | |
return sizeInMegaBytes * 1024 * 1024; | |
} | |
function onDragZoneChange () { | |
document.querySelector(dropZoneSelector).classList.remove('dropzone-active'); | |
document.querySelector(dropZoneSelector).classList.remove('dropzone-badtype'); | |
} | |
function isValidFileItem (fileItem) { | |
return !!(supportedFileTypes.indexOf(fileItem.type) > -1); | |
} | |
function onFileDrop (event) { | |
event.preventDefault(); | |
onDragZoneChange(); | |
const errorOutput = document.querySelector(errorSelector); | |
if (errorOutput) { | |
errorOutput.innerHTML = ' '; | |
} | |
const maxFileSize = toBytes(maxFileSizeMB); | |
let dataTransfer = new DataTransfer(); | |
let tooBig = false; | |
let tooManyFiles = false; | |
if (event.dataTransfer.files) { | |
[...event.dataTransfer.files].forEach((file) => { | |
if (file.size > maxFileSize) { | |
tooBig = true; | |
return; | |
} else if (dataTransfer.items.length > maxFileCount) { | |
tooManyFiles = true; | |
return; | |
} | |
if (isValidFileItem(file) && dataTransfer.items.length < maxFileCount) { | |
dataTransfer.items.add(file); | |
} | |
}); | |
} | |
const fileInput = document.querySelector(fileInputSelector); | |
fileInput.files = dataTransfer.files; | |
fileInput.dispatchEvent(new Event('change', { bubbles: true })); | |
if (errorSelector) { | |
const messages = []; | |
if (tooBig) { | |
messages.push(`Files larger than ${maxFileSizeMB} MB were ignored`); | |
} | |
if (tooManyFiles) { | |
messages.push(`Uploads are limited to ${maxFileCount} files per submission`); | |
} | |
errorOutput.innerHTML = messages.join('; '); | |
} | |
} | |
function onDragOver (event) { | |
event.preventDefault(); | |
event.stopPropagation(); | |
document.querySelector(dropZoneSelector).classList.add('dropzone-active'); | |
let permitted = true; | |
if (event.dataTransfer.items) { | |
[...event.dataTransfer.items].forEach((item) => { | |
if (!isValidFileItem(item)) { | |
permitted = false; | |
} | |
}); | |
} else { | |
[...event.dataTransfer.files].forEach((file) => { | |
if (!isValidFileItem(item)) { | |
permitted = false; | |
} | |
}); | |
} | |
if (!permitted) { | |
document.querySelector(dropZoneSelector).classList.add('dropzone-badtype'); | |
event.dataTransfer.dropEffect = 'none'; | |
event.dataTransfer.effectAllowed = 'none'; | |
} | |
} | |
document.addEventListener('DOMContentLoaded', () => { | |
const dropZone = document.querySelector(dropZoneSelector); | |
if (dropZone) { | |
dropZone.addEventListener('drop', onFileDrop); | |
dropZone.addEventListener('dragenter', onDragOver); | |
dropZone.addEventListener('dragover', onDragOver); | |
dropZone.addEventListener('dragleave', onDragZoneChange); | |
} | |
// logic to prevent items being dropped elsewhere in the page | |
const body = document.querySelector('body'); | |
body.addEventListener('dragover', (event) => { | |
event.preventDefault(); | |
event.stopPropagation(); | |
event.dataTransfer.dropEffect = 'none'; | |
event.dataTransfer.effectAllowed = 'none'; | |
}); | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment