当前位置: 首页 > news >正文

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

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

效果展示

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

实现

分为两步

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

源码如下

upload.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

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

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;
}

相关文章:

  • Sql with as 语句
  • 重读《人件》Peopleware -(6)Ⅰ管理人力资源Ⅴ-帕金森定律重探 Parkinson’s Law Revisited
  • [算法题:快排(一)]颜色分类
  • 【unity游戏开发介绍之UGUI篇】UGUI概述和基础使用
  • ThingsBoard3.9.1 MQTT Topic(1)
  • Apollo源码总结
  • 寻找峰值 --- 二分查找
  • 主流开源大模型评估数据集
  • 【工具】Fiddler抓包
  • 本地部署大模型(ollama模式)
  • 【Code】《代码整洁之道》笔记-Chapter13-并发编程
  • 机械臂只有位置信息是否可以进行手眼标定?
  • HDF5文件格式:数据类型与读写功能详解
  • asm汇编源代码之CPU型号检测
  • Axure中继器(Repeater): 列表多选和 列表查询
  • Python 数据分析01 环境搭建教程
  • SpringBoot项目如何用ServiceLocatorFactoryBean优雅切换支付渠道?
  • FreeRTOS使任务处于运行态的API ?
  • IDEA遇到问题汇总
  • kernel32!GetQueuedCompletionStatus函数分析之返回值得有效性
  • 程序员做图网站/百度基木鱼建站
  • 深圳最好的网站开发公司电话/中国突然宣布大消息
  • 网站官方认证怎么做/茂名网站建设制作
  • 漂亮公司网站源码打包下载/长沙营销推广
  • 电子商务网站建设与维护期末答案/今日财经新闻
  • 宁波住房与城乡建设部网站/郑州竞价托管