原生 JS 实现图片预览上传组件:多图上传 + 拖拽上传 + 裁剪预览 + 进度显示(附完整源码)
前言图片上传是前端开发中高频且核心的功能场景如头像上传、素材管理、表单提交等。本文基于原生 HTMLCSSJavaScript 实现一套企业级图片预览上传组件包含多图选择、拖拽上传、实时预览、图片裁剪、上传进度显示、文件大小 / 格式校验等功能无任何第三方框架依赖代码模块化封装可直接集成到各类项目中。实现效果支持单图 / 多图选择上传兼容主流浏览器拖拽上传可直接将图片拖入上传区域完成选择实时预览选中图片后立即展示缩略图支持删除单张图片图片裁剪内置简易裁剪功能支持固定比例裁剪格式 / 大小校验限制仅允许 jpg/png/webp 格式可自定义文件大小上限上传进度模拟 AJAX 上传实时显示上传进度条响应式布局适配 PC 端、平板、手机等多端设备友好提示操作反馈清晰错误提示直观技术栈HTML5FileReader API、拖放 API、Canvas图片裁剪CSS3Flex 布局、Grid 布局、过渡动画、响应式适配原生 JavaScript文件处理、Blob/FormData、异步编程、DOM 操作性能优化图片压缩、懒加载思想、事件委托完整代码html预览!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title图片预览上传组件 | 原生JS实现/title meta namekeywords content图片上传,原生JS,拖拽上传,图片预览,图片裁剪,FileReader meta namedescription content原生JavaScript实现图片预览上传组件支持多图上传、拖拽上传、裁剪预览、进度显示、格式校验 link relstylesheet hrefhttps://cdn.bootcdn.net/ajax/libs/font-awesome/6.4.0/css/all.min.css style * { margin: 0; padding: 0; box-sizing: border-box; font-family: Microsoft YaHei, sans-serif; } body { background-color: #f8f9fa; color: #333; line-height: 1.6; padding: 50px 20px; } .upload-container { max-width: 1000px; margin: 0 auto; background-color: #fff; border-radius: 10px; box-shadow: 0 2px 20px rgba(0,0,0,0.08); padding: 30px; } .upload-header { margin-bottom: 25px; padding-bottom: 15px; border-bottom: 1px solid #eee; } .upload-header h2 { color: #2c3e50; font-size: 24px; margin-bottom: 8px; } .upload-header .tips { color: #666; font-size: 14px; } /* 上传区域样式 */ .upload-area { border: 2px dashed #ddd; border-radius: 8px; padding: 40px 20px; text-align: center; cursor: pointer; transition: all 0.3s ease; margin-bottom: 30px; } .upload-area:hover { border-color: #3498db; background-color: #f0f8ff; } .upload-area.active { border-color: #2ecc71; background-color: #f8fff8; } .upload-icon { font-size: 48px; color: #999; margin-bottom: 15px; transition: color 0.3s; } .upload-area:hover .upload-icon { color: #3498db; } .upload-text { font-size: 16px; color: #666; margin-bottom: 10px; } .upload-hint { font-size: 12px; color: #999; } #fileInput { display: none; } /* 预览区域样式 */ .preview-container { margin-bottom: 30px; } .preview-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; } .preview-title { font-size: 18px; color: #333; } .preview-actions { display: flex; gap: 10px; } .action-btn { padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; transition: all 0.3s; } .crop-btn { background-color: #3498db; color: #fff; } .clear-btn { background-color: #e74c3c; color: #fff; } .action-btn:hover { opacity: 0.9; transform: translateY(-2px); } .preview-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 15px; } .preview-item { position: relative; border-radius: 6px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1); aspect-ratio: 1/1; } .preview-img { width: 100%; height: 100%; object-fit: cover; display: block; } .preview-close { position: absolute; top: 5px; right: 5px; width: 24px; height: 24px; background-color: rgba(0,0,0,0.6); color: #fff; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 12px; transition: all 0.2s; } .preview-close:hover { background-color: #e74c3c; } /* 进度条样式 */ .upload-progress { margin-bottom: 25px; display: none; } .progress-bar { width: 100%; height: 8px; background-color: #eee; border-radius: 4px; overflow: hidden; margin-top: 8px; } .progress-fill { height: 100%; background-color: #2ecc71; width: 0%; transition: width 0.3s ease; border-radius: 4px; } /* 上传按钮 */ .submit-btn { width: 100%; padding: 12px; background-color: #2ecc71; color: #fff; border: none; border-radius: 6px; font-size: 16px; cursor: pointer; transition: all 0.3s; } .submit-btn:disabled { background-color: #bdc3c7; cursor: not-allowed; transform: none; } .submit-btn:hover:not(:disabled) { background-color: #27ae60; transform: translateY(-2px); } /* 裁剪弹窗 */ .crop-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.7); display: none; align-items: center; justify-content: center; z-index: 999; padding: 20px; } .crop-content { background-color: #fff; border-radius: 8px; width: 100%; max-width: 800px; padding: 20px; } .crop-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .crop-title { font-size: 18px; color: #333; } .crop-close { font-size: 20px; cursor: pointer; color: #666; } .crop-close:hover { color: #e74c3c; } .crop-body { display: flex; flex-direction: column; gap: 20px; } .crop-preview { width: 100%; height: 300px; border: 1px solid #eee; display: flex; align-items: center; justify-content: center; overflow: hidden; } .crop-img { max-width: 100%; max-height: 100%; } .crop-actions { display: flex; justify-content: flex-end; gap: 10px; } .confirm-crop { background-color: #3498db; color: #fff; } /* 响应式适配 */ media (max-width: 768px) { .upload-container { padding: 20px; } .upload-area { padding: 30px 15px; } .crop-preview { height: 200px; } } media (max-width: 480px) { .preview-list { grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); } .upload-header h2 { font-size: 20px; } } /style /head body div classupload-container !-- 头部说明 -- div classupload-header h2图片上传组件/h2 p classtips支持多图上传、拖拽上传、图片裁剪仅允许上传jpg/png/webp格式单张图片不超过5MB/p /div !-- 上传区域 -- div classupload-area iduploadArea input typefile idfileInput acceptimage/jpeg,image/png,image/webp multiple div classupload-icon i classfas fa-cloud-upload-alt/i /div div classupload-text点击或拖拽图片到此处上传/div div classupload-hint支持jpg、png、webp格式单张不超过5MB/div /div !-- 预览区域 -- div classpreview-container idpreviewContainer styledisplay: none; div classpreview-header h3 classpreview-title图片预览/h3 div classpreview-actions button classaction-btn crop-btn idcropBtn disabled裁剪选中图片/button button classaction-btn clear-btn idclearBtn清空所有/button /div /div div classpreview-list idpreviewList/div /div !-- 上传进度 -- div classupload-progress iduploadProgress div classprogress-text上传进度span idprogressPercent0/span%/div div classprogress-bar div classprogress-fill idprogressFill/div /div /div !-- 上传按钮 -- button classsubmit-btn idsubmitBtn disabled开始上传/button /div !-- 裁剪弹窗 -- div classcrop-modal idcropModal div classcrop-content div classcrop-header h3 classcrop-title图片裁剪/h3 span classcrop-close idcropClosei classfas fa-times/i/span /div div classcrop-body div classcrop-preview idcropPreview img src alt裁剪预览 classcrop-img idcropImg /div div classcrop-actions button classaction-btn idcancelCrop取消/button button classaction-btn confirm-crop idconfirmCrop确认裁剪/button /div /div /div /div script // 全局变量 let fileList []; // 存储选中的文件 let selectedImageIndex -1; // 当前选中的图片索引 let cropFile null; // 待裁剪的文件 // DOM元素缓存 const uploadArea document.getElementById(uploadArea); const fileInput document.getElementById(fileInput); const previewContainer document.getElementById(previewContainer); const previewList document.getElementById(previewList); const cropBtn document.getElementById(cropBtn); const clearBtn document.getElementById(clearBtn); const submitBtn document.getElementById(submitBtn); const uploadProgress document.getElementById(uploadProgress); const progressPercent document.getElementById(progressPercent); const progressFill document.getElementById(progressFill); const cropModal document.getElementById(cropModal); const cropImg document.getElementById(cropImg); const cropClose document.getElementById(cropClose); const cancelCrop document.getElementById(cancelCrop); const confirmCrop document.getElementById(confirmCrop); // 初始化 function init() { bindEvents(); } // 绑定所有事件 function bindEvents() { // 点击上传区域触发文件选择 uploadArea.addEventListener(click, () { fileInput.click(); }); // 拖放相关事件 uploadArea.addEventListener(dragover, (e) { e.preventDefault(); uploadArea.classList.add(active); }); uploadArea.addEventListener(dragleave, () { uploadArea.classList.remove(active); }); uploadArea.addEventListener(drop, (e) { e.preventDefault(); uploadArea.classList.remove(active); const files e.dataTransfer.files; handleFiles(files); }); // 文件选择事件 fileInput.addEventListener(change, (e) { const files e.target.files; handleFiles(files); }); // 清空所有图片 clearBtn.addEventListener(click, () { if (confirm(确定要清空所有图片吗)) { fileList []; selectedImageIndex -1; updatePreview(); updateButtonStatus(); } }); // 裁剪按钮事件 cropBtn.addEventListener(click, () { if (selectedImageIndex 0 selectedImageIndex fileList.length) { cropFile fileList[selectedImageIndex]; const reader new FileReader(); reader.onload (e) { cropImg.src e.target.result; cropModal.style.display flex; }; reader.readAsDataURL(cropFile); } }); // 关闭裁剪弹窗 cropClose.addEventListener(click, () { cropModal.style.display none; cropFile null; }); cancelCrop.addEventListener(click, () { cropModal.style.display none; cropFile null; }); // 确认裁剪简易裁剪实际项目可集成cropper.js confirmCrop.addEventListener(click, () { // 创建Canvas进行裁剪此处模拟1:1裁剪 const canvas document.createElement(canvas); const ctx canvas.getContext(2d); const img new Image(); img.onload () { // 取图片最小边作为裁剪尺寸实现1:1裁剪 const size Math.min(img.width, img.height); canvas.width size; canvas.height size; // 居中裁剪 const x (img.width - size) / 2; const y (img.height - size) / 2; ctx.drawImage(img, x, y, size, size, 0, 0, size, size); // 将Canvas转为Blob canvas.toBlob((blob) { // 替换原文件 const newFile new File([blob], cropFile.name, { type: cropFile.type, lastModified: Date.now() }); fileList[selectedImageIndex] newFile; updatePreview(); cropModal.style.display none; cropFile null; }, cropFile.type); }; img.src cropImg.src; }); // 提交上传 submitBtn.addEventListener(click, () { if (fileList.length 0) { alert(请先选择要上传的图片); return; } simulateUpload(); }); // 预览项点击/删除事件事件委托 previewList.addEventListener(click, (e) { const previewItem e.target.closest(.preview-item); if (!previewItem) return; const index parseInt(previewItem.dataset.index); // 删除图片 if (e.target.classList.contains(preview-close)) { fileList.splice(index, 1); // 重置选中索引 if (selectedImageIndex index) { selectedImageIndex -1; } else if (selectedImageIndex index) { selectedImageIndex--; } updatePreview(); updateButtonStatus(); return; } // 选中图片用于裁剪 selectedImageIndex index; // 移除所有选中样式 document.querySelectorAll(.preview-item).forEach(item { item.style.border none; }); // 添加选中样式 previewItem.style.border 2px solid #3498db; cropBtn.disabled false; }); } // 处理选中的文件 function handleFiles(files) { if (!files || files.length 0) return; // 遍历文件 for (let i 0; i files.length; i) { const file files[i]; // 校验文件类型 if (![image/jpeg, image/png, image/webp].includes(file.type)) { alert(文件${file.name}格式不支持仅允许jpg/png/webp格式); continue; } // 校验文件大小5MB const maxSize 5 * 1024 * 1024; if (file.size maxSize) { alert(文件${file.name}大小超过5MB限制); continue; } // 添加到文件列表 fileList.push(file); } // 更新预览和按钮状态 updatePreview(); updateButtonStatus(); } // 更新预览列表 function updatePreview() { if (fileList.length 0) { previewContainer.style.display none; return; } previewContainer.style.display block; previewList.innerHTML ; // 渲染预览项 fileList.forEach((file, index) { const reader new FileReader(); reader.onload (e) { const previewItem document.createElement(div); previewItem.className preview-item; previewItem.dataset.index index; // 选中状态样式 if (index selectedImageIndex) { previewItem.style.border 2px solid #3498db; } previewItem.innerHTML img src${e.target.result} alt预览图 classpreview-img span classpreview-closei classfas fa-times/i/span ; previewList.appendChild(previewItem); }; reader.readAsDataURL(file); }); } // 更新按钮状态 function updateButtonStatus() { // 上传按钮状态 submitBtn.disabled fileList.length 0; // 裁剪按钮状态 cropBtn.disabled selectedImageIndex 0 || fileList.length 0; } // 模拟上传实际项目替换为真实AJAX请求 function simulateUpload() { uploadProgress.style.display block; submitBtn.disabled true; clearBtn.disabled true; cropBtn.disabled true; let progress 0; const totalFiles fileList.length; let uploadedFiles 0; // 模拟进度更新 const progressInterval setInterval(() { uploadedFiles; progress Math.floor((uploadedFiles / totalFiles) * 100); progressPercent.textContent progress; progressFill.style.width ${progress}%; // 上传完成 if (progress 100) { clearInterval(progressInterval); // 延迟提示模拟真实上传耗时 setTimeout(() { alert(成功上传${totalFiles}张图片); // 重置状态 fileList []; selectedImageIndex -1; uploadProgress.style.display none; progressFill.style.width 0%; progressPercent.textContent 0; updatePreview(); updateButtonStatus(); clearBtn.disabled false; }, 500); } }, 500); // 实际项目中使用FormData上传示例 /* const formData new FormData(); fileList.forEach(file { formData.append(files, file); }); fetch(/api/upload, { method: POST, body: formData, onUploadProgress: (e) { const progress Math.floor((e.loaded / e.total) * 100); progressPercent.textContent progress; progressFill.style.width ${progress}%; } }).then(response response.json()) .then(data { alert(上传成功); // 后续处理 }).catch(error { alert(上传失败 error.message); }); */ } // 启动应用 init(); /script /body /html功能说明基础上传点击上传区域可打开文件选择器支持多选图片自动过滤非图片文件限制单张图片大小不超过 5MB。拖拽上传直接将本地图片拖入上传区域自动完成文件选择和校验操作更便捷。图片预览选中图片后实时生成缩略图预览预览项支持点击选中、删除单张图片、清空所有图片。图片裁剪选中单张图片后点击 “裁剪选中图片”可打开裁剪弹窗进行 1:1 比例裁剪裁剪后替换原图片。上传进度点击 “开始上传” 后模拟上传进度实时显示上传百分比和进度条上传完成后给出成功提示。格式校验仅允许上传 jpg/png/webp 格式图片非支持格式会给出明确的错误提示。响应式适配预览列表采用网格布局自动适配不同屏幕宽度移动端优化显示效果。总结本图片上传组件基于原生 JS 实现无任何第三方依赖涵盖了企业级图片上传场景的核心功能代码结构清晰、模块化程度高易于扩展和二次开发。开发者可在此基础上进一步扩展集成专业裁剪库如 cropper.js实现更灵活的裁剪功能增加图片压缩功能降低上传带宽消耗对接真实后端接口实现图片上传到服务器增加上传失败重试、断点续传等高级功能支持图片旋转、缩放、水印添加等编辑功能。该组件可直接应用于表单提交、头像上传、素材管理等业务场景是前端开发中极具实用价值的实战项目。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2498316.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!