Skip to content

Instantly share code, notes, and snippets.

@ajmas
Last active March 11, 2025 17:43
Show Gist options
  • Save ajmas/5710ac15c48b74b806bee9c2073b44dc to your computer and use it in GitHub Desktop.
Save ajmas/5710ac15c48b74b806bee9c2073b44dc to your computer and use it in GitHub Desktop.
Basic HTML Drag and drop for files
.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 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';
});
})
Display the source blob
Display the rendered blob
Raw
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns:xl="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="26.468044 26.468044 69.94291 69.94291" width="69.94291" height="69.94291">
<defs/>
<g id="Canvas_1" fill-opacity="1" stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none">
<title>Canvas 1</title>
<g id="Canvas_1_Layer_1">
<title>Layer 1</title>
<g id="Graphic_5">
<path d="M 90.60765 26.968044 L 95.91096 32.271345 L 32.271345 95.91096 L 26.968044 90.60765 Z" fill="white"/>
<path d="M 90.60765 26.968044 L 95.91096 32.271345 L 32.271345 95.91096 L 26.968044 90.60765 Z" stroke="white" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="Graphic_6">
<path d="M 90.60765 95.91096 L 95.91096 90.60765 L 32.271345 26.968044 L 26.968044 32.271345 Z" fill="white"/>
<path d="M 90.60765 95.91096 L 95.91096 90.60765 L 32.271345 26.968044 L 26.968044 32.271345 Z" stroke="white" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
</g>
</g>
</svg>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment