File Input with Drag & Drop
File input implementation combining traditional upload and drag-drop with unified validation logic.
Unified File Handler
Single handler function processes both input change and drop events with consistent validation.
const input = document.querySelector('input[type="file"]');
const uploadArea = document.getElementById('upload-area');
const img = document.querySelector('img');
input.addEventListener('change', (e) => {
changeHandler({
e,
callback: (image) => {
console.dir(image, { depth: null });
img.src = image.src;
},
});
});
uploadArea.addEventListener("dragenter", (e) => {
e.preventDefault();
});
uploadArea.addEventListener("dragover", (e) => {
e.preventDefault();
});
uploadArea.addEventListener("dragleave", (e) => {
e.preventDefault();
});
uploadArea.addEventListener("drop", (e) => {
e.preventDefault();
changeHandler({
e,
data: e.dataTransfer.files[0],
callback: (image) => {
console.dir(image, { depth: null });
img.src = image.src;
},
});
});
const changeHandler = ({ e, data, callback }) => {
// drag and dropの場合は e.dataTransfer.files[0] を使用
let file = data === undefined ? e.target.files[0] : data;
// 拡張子チェック
if (!file.type.match(/^image\/(png|jpg|jpeg|gif)$/)) {
return;
}
// 容量チェック(10MB)
if (10 * 1024 * 1024 <= file.size) {
return;
}
let image = new Image();
let fileReader = new FileReader();
fileReader.onload = (e) => {
let base64 = e.target.result;
image.onload = () => {
callback(image);
};
image.src = base64;
};
fileReader.readAsDataURL(file);
};
Key implementation details:
- Extracts file from either
e.target.filesore.dataTransfer.files - Validates MIME type against image formats
- Enforces 10MB file size limit
- Converts to base64 using FileReader for preview display
<head>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
</head>
<main class="grid gap-4">
<form action="" class="w-full">
<div id="upload-area" class="sm:mt-0 sm:col-span-2">
<div
class="flex justify-center p-6! border-2 border-gray-300 border-dashed rounded-md"
>
<div class="flex flex-col items-center">
<p class="mt-1 text-sm text-gray-600">
<label
id="original-image-label"
for="original-image-file"
class="cursor-pointer font-medium text-indigo-600 hover:text-indigo-500 focus:outline-none focus:underline transition duration-150 ease-in-out"
>
Upload a file
<input
type="file"
id="original-image-file"
class="hidden"
/>
</label>
or drag and drop
</p>
<p class="mt-1 text-xs text-gray-500">
PNG, JPG, GIF up to 10MB
</p>
</div>
</div>
</div>
</form>
<img src="" alt="">
</main>
<style>
a {
color: #4f46e5; /* Indigo-600 */
}
</style>
<script>
const input = document.querySelector('input[type="file"]');
const uploadArea = document.getElementById('upload-area');
const img = document.querySelector('img');
input.addEventListener('change', (e) => {
changeHandler({
e,
callback: (image) => {
console.dir(image, { depth: null });
img.src = image.src;
},
});
});
uploadArea.addEventListener("dragenter", (e) => {
e.preventDefault();
});
uploadArea.addEventListener("dragover", (e) => {
e.preventDefault();
});
uploadArea.addEventListener("dragleave", (e) => {
e.preventDefault();
});
uploadArea.addEventListener("drop", (e) => {
e.preventDefault();
changeHandler({
e,
data: e.dataTransfer.files[0],
callback: (image) => {
console.dir(image, { depth: null });
img.src = image.src;
},
});
});
const changeHandler = ({ e, data, callback }) => {
// drag and dropの場合は e.dataTransfer.files[0] を使用
let file = data === undefined ? e.target.files[0] : data;
// 拡張子チェック
if (!file.type.match(/^image\/(png|jpg|jpeg|gif)$/)) {
return;
}
// 容量チェック(10MB)
if (10 * 1024 * 1024 <= file.size) {
return;
}
let image = new Image();
let fileReader = new FileReader();
fileReader.onload = (e) => {
let base64 = e.target.result;
image.onload = () => {
callback(image);
};
image.src = base64;
};
fileReader.readAsDataURL(file);
};
</script>