该项目涉及到的一些关键知识点
选择多个文件
在 JavaScript 中,要实现选择多个文件,可以使用 HTML 的<input>
元素的multiple
属性。以下是示例代码:
<input type="file" multiple>
选择文件夹
在 JavaScript 中要实现选择文件夹的功能,由于浏览器的安全限制,直接选择文件夹可能具有一定的局限性。
- webkitdirectory属性适用于部分现代浏览器
- mozdirectory属性适配火狐浏览器
- odirectory属性适配 Opera 内核浏览器
以下是示例代码:
<input type="file" webkitdirectory mozdirectory odirectory>
拖拽文件或文件夹
在 JavaScript 中实现拖拽文件或文件夹的交互,主要涉及以下几个关键步骤和知识点:ondrop 事件
定义和用法:
当被拖动的元素或选取的文本被放置在目标区域时,会触发 ondrop
事件。它通常与拖放功能结合使用,用于在元素被放置到特定区域时执行相应的操作。
触发条件:
在拖放操作中,当拖动的元素或文本被释放到目标区域时,就会触发 ondrop
事件。
相关事件:
在拖放过程中,还会涉及其他相关事件,如:
ondragstart
:在拖动开始时触发。ondrag
:在拖动过程中持续触发。ondragend
:在拖动结束时触发。ondragenter
:当拖动元素进入目标区域时触发。ondragover
:当拖动元素在目标区域上移动时触发,为了使ondrop
事件能够正常触发,通常需要在ondragover
事件处理程序中调用event.preventDefault()
来阻止浏览器的默认行为。ondragleave
:当拖动元素离开目标区域时触发。
上传前
上传中
上传结束
部分重要代码如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--告诉IE浏览器以最高版本的标准模式来渲染页面-->
<meta http-equiv="x-ua-compatible" content="IE=edge">
<!--width=device-width 表示将视口的宽度设置为设备的屏幕宽度-->
<!--“initial-scale=1.0 初始的缩放比例为1.0-->
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>图片上传demo</title>
<link rel="stylesheet" href="./static/font_4633578_xmf7zv1kiyn/iconfont.css">
<style>
.main {
width: 800px;
height: 300px;
position: absolute;
top: 20%;
left: 50%;
transform: translate(-50%, -50%);
}
.container {
border: 2px dashed #808080;
width: 100%;
height: 150px;
margin: 20px auto;
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
}
#chooseFile,#chooseDir {
display: none;
}
.my_click, .startUploads {
width: 100px;
height: 40px;
font-size: 15px;
line-height: 40px;
background-color: #1E90FF;
color: white;
border: #1E90FF;
outline: none;
margin-bottom: 20px;
}
table {
width: 100%;
border-collapse: collapse;
background-color: #F9F9F9;
border: 1px solid #D3D3D3;
opacity: 0.7;
overflow: auto;
max-height: 300px;
margin-bottom: 20px;
}
.table-header {
height: 50px;
}
th,
td {
border: 1px solid #D3D3D3;
padding: 15px;
text-align: left;
font-size: 14px;
}
th {
background-color: #EDEDED;
color: black;
font-size: 14px;
}
th:nth-child(1),
td:nth-child(1) {
width: 15%;
}
th:nth-child(2),
td:nth-child(2) {
width: 10%;
}
th:nth-child(3),
td:nth-child(3) {
width: 13%;
}
th:nth-child(5),
td:nth-child(5) {
width: 10%;
}
th:nth-child(4),
td:nth-child(4) {
width: 50%;
}
.progress {
background-color: #f0f0f0;
width: 100%;
height: 20px;
border-radius: 5px;
overflow: hidden;
position: relative;
}
.progress-bar {
height: 100%;
background-color: #4CAF50;
}
.progress-percentage {
position: absolute;
top: 0;
right: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: white;
}
.delete {
border: none;
background-color: #F9F9F9;
}
.icon-shangchuan {
font-size: 30px;
display: inline-block;
vertical-align: middle;
}
.text {
font-size: 14px;
}
.icon-no-data {
font-size: 50px;
opacity: 0.4;
}
.no-data {
position: relative;
}
.no-data i {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
margin: 0;
}
</style>
</head>
<body>
<div class="main">
<div class="container">
<div>
<i class="iconfont icon-shangchuan"></i>
<i class="text">将目录或者多个文件拖拽到此进行扫描</i><br>
<i class="text">支持的文件类型:.jpg、jpeg、.bmp、webp、.gif、.png</i><br>
<i class="text">每个文件允许的最大尺寸:1M</i>
</div>
</div>
<!--multiple属性则允许用户同时选择多个文件-->
<button onclick="chooseFile.click()" class="my_click">选择文件</button>
<button onclick="chooseDir.click()" class="my_click">选择文件夹</button>
<input id="chooseFile" type="file" multiple onchange="handleFileUpload()">
<input id="chooseDir" type="file" webkitdirectory mozdirectory odirectory onchange="handleDirUpload()">
<table id="fileTable">
<tr class="table-header">
<th>文件名</th>
<th>类型</th>
<th>大小</th>
<th>状态</th>
<th>操作</th>
</tr>
</table>
<button class="startUploads" onclick="uploadAllFiles()">开始上传</button>
</div>
</body>
<script>
const MAX_FILE_SIZE = 1024 * 1024; // 1MB 大小限制
let selectedFiles = []; //所有要上传的文件
const typesList = ['image/jpeg'];
const div = document.querySelector('.container');
div.ondragenter = (e) => {
e.preventDefault();
console.log('上传文件已经拖入');
};
div.ondragover = (e) => {
e.preventDefault();
console.log('上传文件悬停中');
};
function handleNoDataRow() {
const table = document.getElementById('fileTable');
const rows = table.rows;
let hasData = false;
for (let i = 1; i < rows.length; i++) { // 从第二行开始检查,跳过表头
if (rows[i].cells[0].textContent.trim() !== '' && rows.length > 2) {
hasData = true;
break;
}
}
const noDataRow = document.querySelector('.no-data');
if (!hasData && !noDataRow) {
const newNoDataRow = document.createElement('tr');
newNoDataRow.className = 'no-data';
newNoDataRow.style.height = '50px';
const newNoDataCell = document.createElement('td');
newNoDataCell.colSpan = 5;
const icon = document.createElement('i');
icon.className = 'iconfont icon-no-data';
newNoDataCell.appendChild(icon);
newNoDataRow.appendChild(newNoDataCell);
table.appendChild(newNoDataRow);
} else if (hasData && noDataRow) { // 如果有数据且存在 no-data 行,则删除
noDataRow.remove();
}
}
handleNoDataRow();
function removeFileByField(selectedFiles, fieldName, valueToRemove) {
return selectedFiles.filter(function (item) {
return item[fieldName] !== valueToRemove;
});
}
function deleteFile(button) {
const row = button.parentNode.parentNode;
const fileName = row.cells[0].textContent;
const xhr = new XMLHttpRequest();
const url = 'http://attic.niuzheng.net/deleteUpload.php?fileName=' + encodeURIComponent(fileName);
xhr.open('GET', url, true);
xhr.onload = function () {
if (xhr.status === 200) {
handleNoDataRow();
selectedFiles = removeFileByField(selectedFiles, 'name', fileName)
row.parentNode.removeChild(row);
} else {
alert('删除文件时出错!');
}
};
xhr.send();
}
function handleDirUpload()
{
const files = document.getElementById('chooseDir').files;
handleChooseFilesOrDir(files)
handleNoDataRow();
}
function handleFileUpload() {
const files = document.getElementById('chooseFile').files;
handleChooseFilesOrDir(files)
handleNoDataRow();
}
function handleChooseFilesOrDir(files){
for (const file of files) {
if (!typesList.includes(file.type)) {
alert('禁止上传该类型文件!');
continue;
}
if (file.size > MAX_FILE_SIZE) {
alert('文件大小超过限制!');
continue;
}
selectedFiles.push(file);
addFileToTable(file);
}
}
function addFileToTable(file) {
const table = document.getElementById('fileTable');
const newRow = table.insertRow(-1);
const fileNameCell = newRow.insertCell(0);
fileNameCell.textContent = file.name;
const fileTypeCell = newRow.insertCell(1);
fileTypeCell.textContent = file.type;
const fileSizeCell = newRow.insertCell(2);
const sizeInM = (file.size / (1024 * 1024)).toFixed(2);
fileSizeCell.textContent = `${sizeInM} M`;
const fileStatusCell = newRow.insertCell(3);
const progressDiv = document.createElement('div');
progressDiv.className = 'progress';
const progressBar = document.createElement('div');
progressBar.className = 'progress-bar';
progressBar.style.width = '0%';
const progressPercentage = document.createElement('div');
progressPercentage.className = 'progress-percentage';
progressPercentage.textContent = '0%';
progressPercentage.style.color = '#4CAF50';
progressDiv.appendChild(progressBar);
progressDiv.appendChild(progressPercentage);
fileStatusCell.appendChild(progressDiv);
const fileOperationCell = newRow.insertCell(4);
fileOperationCell.innerHTML = `<button class="delete iconfont icon-shanchu1" onclick="deleteFile(this)"></button>`;
// 当添加文件时显示表格并隐藏 no-data 元素
document.getElementById('fileTable').style.display = 'table';
}
function uploadAllFiles() {
if (selectedFiles.length <= 0) {
alert('请选择要上传的图片')
}
for (const file of selectedFiles) {
let index = selectedFiles.indexOf(file)+1;
let indexRow = document.getElementById('fileTable').rows[index];
if (!indexRow) continue;
const xhr = new XMLHttpRequest();
const url = 'http://attic.niuzheng.net/upload.php';
xhr.open('POST', url, true);
xhr.upload.addEventListener('progress', (event) => updateUploadProgress(event, indexRow.cells[3].firstChild));
xhr.onload = function () {
if (xhr.status === 200) {
console.log('Upload succeeded');
} else {
console.log('Upload failed');
}
};
// 发送文件数据
const data = new FormData();
data.append('file', file);
xhr.send(data);
// xhr.abort();
}
}
function updateUploadProgress(event, progressDiv) {
if (event.lengthComputable) {
const percentComplete = (event.loaded / event.total) * 100;
const progressBar = progressDiv.querySelector('.progress-bar');
progressBar.style.width = `${percentComplete}%`;
const progressPercentage = progressDiv.querySelector('.progress-percentage');
progressPercentage.textContent = `${percentComplete.toFixed(2)}%`;
progressPercentage.style.color = 'white';
}
}
div.ondrop = (e) => {
e.preventDefault();
console.log('上传文件已放开');
const items = e.dataTransfer.items;
const queue = [];
const processItem = (item) => {
const entry = item.webkitGetAsEntry();
if (!entry.isDirectory) {
entry.file((file) => {
if (!typesList.includes(file.type)) {
alert('禁止上传该类型文件!');
return;
}
if (file.size > MAX_FILE_SIZE) {
alert('文件大小超过限制!');
return;
}
selectedFiles.push(file);
addFileToTable(file);
});
} else if (entry.isDirectory) {
queue.push(entry)
}
}
const processQueue = () => {
while (queue.length > 0) {
const entry = queue.shift();
const reader = entry.createReader();
reader.readEntries(entries => {
for (const subEntry of entries) {
if (subEntry.isFile) {
subEntry.file((file) => {
selectedFiles.push(file);
addFileToTable(file);
});
} else if (subEntry.isDirectory) {
queue.push(subEntry);
}
}
processQueue();
});
}
};
for (const item of items) {
processItem(item);
}
processQueue();
};
</script>
</html>
获取完整代码
关注 公共号,回复 rule20240228
获取。
Comment here is closed