JS实现文件点击或者拖拽上传

B站看到了渡一大师课的切片,自己实现了一下,做下记录

效果展示

分为上传前、上传中和上传后

实现

分为两步

  1. 界面交互
  2. 网络请求

源码如下

upload.html

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>图片上传 Demo</title>
		<link rel="stylesheet" href="upload.css" />
	</head>
	<body>
		<h1>图片上传 Demo</h1>
		<div class="upload select">
			<div class="upload-select"><input type="file" accept="image/*" / ></div>
			<div class="upload-progress">
				<div class="upload-progress-bar"></div>
				<div class="upload-progress-text">文件上传中...</div>
				<button>取消</button>
			</div>
			<div class="upload-result">
				<button>删除</button>
				<img src="" alt="" class="preview" />
			</div>
		</div>

		<script src="upload.js"></script>
	</body>
</html>

upload.js

js 复制代码
document.addEventListener('DOMContentLoaded', function () {
	const $ = document.querySelector.bind(document);
	const doms = {
		img: $('.preview'),
		container: $('.upload'),
		select: $('.upload-select'),
		selectFile: $('.upload-select input'),
		progress: $('.upload-progress'),
		cancelBtn: $('.upload-progress button'),
		delBtn: $('.upload-result button'),
	};
	// 备用方案,不利用input拖拽,将input设置为none
	// doms.select.ondragenter = function (e) {
	// 	e.preventDefault();
	// };

	// doms.select.ondragover = function (e) {
	// 	e.preventDefault();
	// };

	// doms.select.ondrop = function (e) {
	// 	e.preventDefault();
	// 	const file = e.dataTransfer.files[0];
	// 	if (!validateFile(file)) {
	// 		return;
	// 	}
	// 	doms.selectFile.files = e.dataTransfer.files;
	// 	doms.selectFile.onchange();
	// };

	// 切换三个子界面
	function showArea(areaName) {
		doms.container.className = `upload ${areaName}`;
	}
	// 设置进度
	function setProgress(value) {
		doms.progress.style.setProperty('--progress', value + '%');
	}
	// 取消上传
	let cancelUpload = null;
	function cancel() {
		cancelUpload && cancelUpload(); // 取消网络传输
		showArea('select');
		doms.selectFile.value = '';
	}

	// 上传文件的文件变化
	doms.selectFile.onchange = function () {
		if (this.files.length === 0) {
			return;
		}

		const file = this.files[0];
		console.log(file);
		if (!validateFile(file)) {
			return;
		}
		// 切换界面
		showArea('progress');
		// 显示预览图
		const reader = new FileReader();
		reader.onload = function (e) {
			doms.img.src = e.target.result;
		};
		reader.readAsDataURL(file); // 异步的,结果需要上边的监控拿到
		upload(
			file,
			function (val) {
				setProgress(val);
			},
			function (res) {
				showArea('result');
			},
		);
	};

	// 上传文件
	function upload(file, onProgress, onFinish) {
		const xhr = new XMLHttpRequest();
		xhr.onload = function () {
			const resp = JSON.parse(xhr.responseText);
			onFinish(resp);
		};
		xhr.upload.onprogress = function (e) {
			if (e.lengthComputable) {
				const percent = Math.round((e.loaded / e.total) * 100);
				onProgress(percent);
			}
		};
		xhr.open('POST', '/upload');
		const form = new FormData();
		form.append('avatar', file);
		xhr.send(form);
	}

	// 校验
	function validateFile(file) {
		const maxSize = 1024 * 1024 * 2;
		if (file.size > maxSize) {
			alert('文件大小不能超过2M');
			return false;
		}

		const allowTypes = ['image/jpeg', 'image/png', 'image/gif'];
		if (!allowTypes.includes(file.type)) {
			alert('文件类型只能是jpg、png、gif');
			return false;
		}
		return true;
	}

	doms.cancelBtn.onclick = doms.delBtn.onclick = cancel;
});

upload.css

css 复制代码
body {
	font-family: Arial, sans-serif;
	margin: 0;
	padding: 0;
}
.upload {
	width: 400px;
	height: 400px;
	background-color: azure;
}

/* 通过属性控制子组件显示 */
/* 当父元素有 select 类时,只显示上传选择区域 */
.upload.select .upload-select {
	display: flex;
}

.upload.select .upload-progress,
.upload.select .upload-result {
	display: none;
}

/* 当父元素有 progress 类时,只显示进度条 */
.upload.progress .upload-progress {
	display: flex;
}

.upload.progress .upload-select,
.upload.progress .upload-result {
	display: none;
}

/* 当父元素有 result 类时,只显示结果区域 */
.upload.result .upload-result {
	display: flex;
}

.upload.result .upload-select,
.upload.result .upload-progress {
	display: none;
}

.upload-select {
	height: 100%;
	width: 100%;
	display: flex;
	justify-content: center;
	align-items: center;
	position: relative;
	background-image: url(./fileUplaod.svg);
	background-position: center;
	background-repeat: no-repeat;
}

/*  本身就支持拖拽 */
.upload-select input {
	display: block;
	width: 100%;
	height: 100%;
	opacity: 0;
	cursor: pointer;
}

.upload-progress {
	--progress: 0%;
	height: 100%;
	width: 100%;
	display: flex;
	flex-direction: column;
	justify-content: center;
	align-items: center;
}

.upload-progress-bar {
	width: var(--progress);
	height: 10px;
	background-color: #4caf50;
	transition: width 0.3s ease;
}

.upload-result {
	height: 100%;
	width: 100%;
	display: flex;
	flex-direction: column;
	justify-content: center;
}

.preview {
	max-width: 90%;
	max-height: 90%;
	width: auto;
	height: auto;
	/* 以下属性确保图片居中显示 */
	display: block;
	margin: 0 auto;
	/* 保持宽高比 */
	object-fit: contain;
}
相关推荐
Captaincc20 分钟前
OpenAI以API的形式发布了三 个新模型:GPT-4.1、GPT-4.1 mini 和 GPT-4.1 nano
前端·openai
HelloRevit1 小时前
Next.js 快速启动模板
开发语言·javascript·ecmascript
A-Kamen2 小时前
Webpack vs Vite:深度对比与实战示例,如何选择最佳构建工具?
前端·webpack·node.js
codingandsleeping2 小时前
OPTIONS 预检请求
前端·网络协议·浏览器
程序饲养员3 小时前
ReactRouter7.5: NavLink 和 Link 的区别是什么?
前端·javascript·react.js
小小小小宇4 小时前
CSS 层叠上下文总结
前端
拉不动的猪4 小时前
设计模式之------命令模式
前端·javascript·面试
Json____4 小时前
springboot框架集成websocket依赖实现物联网设备、前端网页实时通信!
前端·spring boot·websocket·实时通信
uhakadotcom4 小时前
Bun vs Node.js:何时选择 Bun?
前端·javascript·面试
前端snow4 小时前
前端工程师看docker是什么?
前端·javascript·docker