Skip to content

Instantly share code, notes, and snippets.

@ajmas
Last active March 11, 2025 17:43
Show Gist options
  • Select an option

  • Save ajmas/5710ac15c48b74b806bee9c2073b44dc to your computer and use it in GitHub Desktop.

Select an option

Save ajmas/5710ac15c48b74b806bee9c2073b44dc to your computer and use it in GitHub Desktop.
Basic HTML Drag and drop for files
/**
This is a rudementary implementation to allow drag an drop upload on an HTML page.
- 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