原生 JS 实现图片预览上传组件:多图上传 + 拖拽上传 + 裁剪预览 + 进度显示(附完整源码)

news2026/4/9 4:47:12
前言图片上传是前端开发中高频且核心的功能场景如头像上传、素材管理、表单提交等。本文基于原生 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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…