如何使用HTML/CSS/JavaScript实现本地文件选择并在网页列表展示?
实现方案全指南
前端和本地设备交互读取文件、展示列表完全基于浏览器原生 File API 实现,不需要额外引入第三方依赖,所有操作默认在浏览器本地完成,不会主动上传文件到服务器。
功能拆分实现
1. 标签切换模块
用两个按钮对应两个标签页,点击按钮切换对应内容区的显示状态即可。
2. 文件上传标签功能
支持点击选择和拖拽选择两种方式,核心逻辑是获取用户选中的File对象列表:
- 点击选择:使用原生
<input type="file" multiple>标签,将其设为透明隐藏,点击自定义按钮时触发该input的点击事件,监听input的change事件即可拿到选中的文件列表e.target.files - 拖拽选择:给拖拽区域绑定三个事件即可实现:
const dropArea = document.querySelector('.upload-area') // 阻止浏览器默认拖拽行为(否则会直接打开文件) ['dragover', 'dragleave', 'drop'].forEach(evt => { dropArea.addEventListener(evt, e => e.preventDefault()) }) // 拖拽悬浮样式切换 dropArea.addEventListener('dragover', () => dropArea.classList.add('hover')) dropArea.addEventListener('dragleave', () => dropArea.classList.remove('hover')) // 接收拖拽的文件 dropArea.addEventListener('drop', e => { const files = e.dataTransfer.files processFiles(files) dropArea.classList.remove('hover') })
3. 可用文件列表展示功能
拿到File对象后,可以直接读取内置属性拼接列表:
- 存储文件:定义全局数组
const fileList = []存储所有选中的文件,新增文件前可根据文件名+文件大小+最后修改时间判断去重,避免重复添加 - 格式化展示字段:
- 文件名:
file.name - 文件大小:将
file.size(单位字节)转成KB/MB格式,示例转换函数:function formatFileSize(bytes) { if (bytes < 1024) return `${bytes} B` if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB` return `${(bytes / (1024 * 1024)).toFixed(2)} MB` } - 修改时间:
new Date(file.lastModified).toLocaleString()转成本地可读时间格式 - 文件类型:
file.type或者截取文件名后缀判断
- 文件名:
- 渲染列表:遍历
fileList数组,把每个文件的属性拼接成DOM元素插入到列表容器即可。
最小可运行示例
直接复制下面代码保存为html文件打开即可看到完整效果:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>文件管理</title> <style> * {margin: 0; padding: 0; box-sizing: border-box; font-family: sans-serif;} .container {max-width: 800px; margin: 30px auto; padding: 0 20px;} /* 标签样式 */ .tabs {display: flex; border-bottom: 1px solid #eee; margin-bottom: 20px;} .tab-btn {padding: 12px 24px; border: none; background: none; font-size: 16px; cursor: pointer; border-bottom: 2px solid transparent;} .tab-btn.active {border-bottom-color: #2385bb; color: #2385bb; font-weight: 500;} .tab-content {display: none;} .tab-content.active {display: block;} /* 上传区域样式 */ .upload-area {border: 2px dashed #ccc; border-radius: 8px; padding: 60px 20px; text-align: center; cursor: pointer;} .upload-area.hover {border-color: #2385bb; background: #f0f8ff;} .upload-area input {display: none;} /* 文件列表样式 */ .file-item {display: flex; justify-content: space-between; align-items: center; padding: 12px; border-bottom: 1px solid #eee;} .file-info {display: flex; flex-direction: column; gap: 4px;} .file-name {font-weight: 500;} .file-meta {font-size: 14px; color: #666; display: flex; gap: 12px;} </style> </head> <body> <div class="container"> <div class="tabs"> <button class="tab-btn active" data-tab="upload">文件上传</button> <button class="tab-btn" data-tab="files">可用文件</button> </div> <!-- 上传标签内容 --> <div class="tab-content active" id="upload"> <div class="upload-area"> <input type="file" id="fileInput" multiple> <p>拖拽文件到此处,或点击选择本地文件</p> </div> </div> <!-- 文件列表标签内容 --> <div class="tab-content" id="files"> <div id="fileList"></div> </div> </div> <script> const fileList = [] const tabBtns = document.querySelectorAll('.tab-btn') const tabContents = document.querySelectorAll('.tab-content') const uploadArea = document.querySelector('.upload-area') const fileInput = document.getElementById('fileInput') const fileListDom = document.getElementById('fileList') // 标签切换逻辑 tabBtns.forEach(btn => { btn.addEventListener('click', () => { const targetTab = btn.dataset.tab tabBtns.forEach(b => b.classList.remove('active')) tabContents.forEach(c => c.classList.remove('active')) btn.classList.add('active') document.getElementById(targetTab).classList.add('active') }) }) // 点击上传区域触发文件选择 uploadArea.addEventListener('click', () => fileInput.click()) fileInput.addEventListener('change', e => processFiles(e.target.files)) // 拖拽逻辑 ['dragover', 'dragleave', 'drop'].forEach(evt => { uploadArea.addEventListener(evt, e => e.preventDefault()) }) uploadArea.addEventListener('dragover', () => uploadArea.classList.add('hover')) uploadArea.addEventListener('dragleave', () => uploadArea.classList.remove('hover')) uploadArea.addEventListener('drop', e => { processFiles(e.dataTransfer.files) uploadArea.classList.remove('hover') }) // 处理选中的文件 function processFiles(files) { Array.from(files).forEach(file => { // 简单去重:判断文件名+大小+修改时间一致则不添加 const isExist = fileList.some(item => item.name === file.name && item.size === file.size && item.lastModified === file.lastModified ) if (!isExist) fileList.push(file) }) renderFileList() } // 格式化文件大小 function formatFileSize(bytes) { if (bytes < 1024) return `${bytes} B` if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB` return `${(bytes / (1024 * 1024)).toFixed(2)} MB` } // 渲染文件列表 function renderFileList() { if (fileList.length === 0) { fileListDom.innerHTML = '<p style="text-align: center; padding: 40px 0; color: #999;">暂无文件</p>' return } fileListDom.innerHTML = fileList.map(file => ` <div class="file-item"> <div class="file-info"> <div class="file-name">${file.name}</div> <div class="file-meta"> <span>${formatFileSize(file.size)}</span> <span>${new Date(file.lastModified).toLocaleString()}</span> <span>${file.type || '未知类型'}</span> </div> </div> </div> `).join('') } // 初始化渲染空列表 renderFileList() </script> </body> </html>
内容的提问来源于stack exchange,提问作者noobmaster
相关产品推荐
相关产品推荐

