原生JavaScript为多图上传预览添加图片移除选项
纯原生多图上传删除功能实现方案
核心约束说明
原生DOM提供的input.files返回的是只读FileList结构,没有提供直接删除单文件的接口,因此需要通过自定义维护可变文件数组的方式实现删除逻辑,全程不需要依赖jQuery。
实现思路
- 单独维护一个可变的文件数组,作为实际待上传的数据源,替代只读的原生FileList
- 渲染预览时遍历自定义数组,为每个预览项绑定对应文件的删除按钮
- 点击删除按钮时,从自定义数组中移除匹配的文件对象,再触发预览重新渲染
- 表单提交前,通过
DataTransfer对象将过滤后的自定义数组同步为input的files属性,确保被删除的文件不会随表单提交到服务端
完整改造代码
改造后的JavaScript逻辑
// 自定义维护的待上传文件数组(替代只读的原生FileList) let selectedFiles = []; const input = document.querySelector('input'); const preview = document.querySelector('.preview'); input.style.opacity = 0; // 文件选择回调 input.addEventListener('change', (e) => { // 将新选择的文件追加到自定义数组,不会覆盖之前已选的文件 const newSelected = Array.from(e.target.files); selectedFiles = [...selectedFiles, ...newSelected]; // 重新渲染预览 updateImageDisplay(); }); // 表单提交前同步过滤后的文件到input元素 document.querySelector('form').addEventListener('submit', () => { const dataTransfer = new DataTransfer(); selectedFiles.forEach(file => dataTransfer.items.add(file)); // 赋值后,表单提交时只会携带经过删除筛选的文件 input.files = dataTransfer.files; }); function updateImageDisplay() { // 清空预览区域 while(preview.firstChild) { preview.removeChild(preview.firstChild); } if(selectedFiles.length === 0) { const para = document.createElement('p'); para.textContent = 'No files currently selected for upload'; preview.appendChild(para); return; } const list = document.createElement('ol'); preview.appendChild(list); for(const file of selectedFiles) { const listItem = document.createElement('li'); const para = document.createElement('p'); // 通用删除按钮生成逻辑 const deleteBtn = document.createElement('button'); deleteBtn.textContent = '删除'; deleteBtn.className = 'delete-btn'; deleteBtn.addEventListener('click', () => { // 按文件对象引用匹配删除,同名同大小文件也不会误删 selectedFiles = selectedFiles.filter(item => item !== file); // 重新渲染预览 updateImageDisplay(); }); if(validFileType(file)) { para.textContent = `File name ${file.name}, file size ${returnFileSize(file.size)}.`; const image = document.createElement('img'); image.src = URL.createObjectURL(file); listItem.appendChild(image); listItem.appendChild(para); } else { para.textContent = `File name ${file.name}: Not a valid file type. Update your selection.`; } // 不管是有效图片还是无效文件,都加上删除按钮 listItem.appendChild(deleteBtn); list.appendChild(listItem); } } const fileTypes = [ 'image/apng', 'image/bmp', 'image/gif', 'image/jpeg', 'image/pjpeg', 'image/png', 'image/svg+xml', 'image/tiff', 'image/webp', 'image/x-icon' ]; function validFileType(file) { return fileTypes.includes(file.type); } function returnFileSize(number) { if(number < 1024) { return number + 'bytes'; } else if(number >= 1024 && number < 1048576) { return (number/1024).toFixed(1) + 'KB'; } else if(number >= 1048576) { return (number/1048576).toFixed(1) + 'MB'; } }
适配的CSS样式
form { width: 600px; background: #ccc; margin: 0 auto; padding: 20px; border: 1px solid black; } form ol { padding-left: 0; } form li, div > p { background: #eee; display: flex; justify-content: space-between; margin-bottom: 10px; list-style-type: none; border: 1px solid black; } form img { height: 64px; order: 1; } form p { line-height: 32px; padding-left: 10px; } /* 新增:删除按钮对齐样式 */ form .delete-btn { align-self: center; margin-right: 10px; } form label, form button { background-color: #7F9CCB; padding: 5px 10px; border-radius: 5px; border: 1px ridge black; font-size: 0.8rem; height: auto; } form label:hover, form button:hover { background-color: #2D5BA3; color: white; } form label:active, form button:active { background-color: #0D3F8F; color: white; }
原有HTML结构无需额外修改
<form> <div> <label for="image_uploads">Choose images to upload (PNG, JPG)</label> <input type="file" id="image_uploads" name="image_uploads" accept=".jpg, .jpeg, .png" multiple> </div> <div class="preview"> <p>No files currently selected for upload</p> </div> <div> <button>Submit</button> </div> </form>
实现注意点
- 删除时通过文件对象引用匹配,不需要依赖文件名、大小等可能重复的字段,不会出现删错项的问题
DataTransfer对象兼容所有现代浏览器,不需要引入额外依赖- 支持多次选择文件追加的场景,不会因为重新选文件覆盖之前已选中的待上传文件
- 无效格式的文件也可以通过删除按钮移除,不会卡在预览列表里
- 如需优化内存占用,可在删除对应预览项时,调用
URL.revokeObjectURL(预览img的src)释放Blob内存
内容的提问来源于stack exchange,提问作者1878castle
相关产品推荐
相关产品推荐

