文件上传2-前端简单上传
无论如何,上传文件操作与后端是紧密相关的,我们应该先保证后端上传文件接口是没有问题,确定参数,返回的响应类型是什么,最好测试调通之后,再进行前端操作。
了解了基本理论,以及有了后端接口并且测试通过了之后,接下来,就是前端的任务了。
其实我们理清思路,前端上传文件给后端,其实无非就是两个点:
1、显示样式 2、传递方式
如果说,我们不在意样式,传递方式直接使用form
表单,这就根本轮不到前端啥事了,直接在form
标签中加上enctype
完事。
<form action="http://127.0.0.1:3002/upload/single" method="post" enctype="multipart/form-data">
<input type="file" name="avatar" id="avatar">
<button type="submit">提交</button>
</form>
当然,前端现在的传递至少至少都应该是ajax的方式,因此我们需要知道ajax上传文件是怎么处理的--------其实和以前一样处理,没有差别
但是唯一的不同是什么?
以前我们大多数时候是get
请求,并且传递的是普通值
现在我们是post
请求,并且传递的是文件
但是大家还记得最开始我们讲解的原理吗?
头信息中必须要有
Content-Type:multipart/form-data; boundary=----分隔符
, 请求体是用分隔符隔开的一个个需要上传的数据
也就是说,ajax基本操作都没变,变化是传递数据的时候加上头信息,加上特殊的请求体就行。
但是这个请求体弄起来很麻烦,所以,浏览器考虑到了这个问题,我们可以使用FormData
对象,我们只需要实例化这个FormData
对象,它就会帮我们创建上传文件的头信息,以及组装请求体。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="http://127.0.0.1:3002/upload/single" method="post" enctype="multipart/form-data">
<input type="file" name="avatar" id="avatar">
<button type="submit">提交</button>
</form>
<hr>
<button id="ajaxUpload" type="button">提交</button>
</body>
<script>
const ajaxUpload = document.getElementById('ajaxUpload');
ajaxUpload.addEventListener('click', function (e) {
e.preventDefault();
const avatar = document.getElementById('avatar');
if(avatar.files.length === 0){
alert("请选择文件");
return;
}
// 创建一个FormData对象
// FormData对象的作用其实就是帮我们生成Content-Type为multipart/form-data的请求体形态
const formData = new FormData();
//有数据就往FormData中append添加就行
//会帮助形成带有boundary分隔符的请求体
formData.append('avatar', avatar.files[0]);
let xhr = new XMLHttpRequest();
xhr.open('POST', 'http://127.0.0.1:3002/upload/single');
//当xhr.readyState的值为4时,触发onload事件
xhr.onload = function () {
console.log(xhr.responseText);
}
//发送formData数据
xhr.send(formData);
})
</script>
</html>
可以看到上面的ajax上传文件,和普通的ajax请求,其实并没有太大的差别,唯一多的就是FormData
帮我们发送上传文件的请求头和组装请求体,其实也就多了3句关键代码
const formData = new FormData();
formData.append('avatar', avatar.files[0]);
......
xhr.send(formData);
有了这个基本操作之后,我们接下来如果想要把界面美化,那多数都是HTML+CSS+JS的基本操作了,和上传文件就没有太多的关系了。
比如现在要实现如下效果:
看着好像很难,其实重要的内容我们大部分都已经实现了,其他基本都是html+css
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://kit.fontawesome.com/56085ed2ba.js" crossorigin="anonymous"></script>
<style>
.file-upload {
position: relative;
display: inline-block;
padding: 30px;
border: 2px dashed #ccc;
border-radius: 5px;
text-align: center;
font-size: 18px;
color: #ccc;
cursor: pointer;
transition: all 0.3s ease;
}
.file-upload:hover {
border-color: #007bff;
color: #007bff;
}
.file-upload input[type="file"] {
position: absolute;
top: 0;
left: 0;
opacity: 0;
cursor: pointer;
width: 100%;
height: 100%;
}
.file-upload .file-upload-icon i {
font-size: 48px;
color: #ccc;
}
.file-upload-text {
margin-top: 20px;
}
.file-upload-progress {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 5px;
background-color: #f5f5f5;
border-radius: 5px;
/* overflow: hidden; */
display: none;
}
.file-upload-progress-bar {
position: absolute;
top: 0;
left: 0;
width: 0;
height: 100%;
background-color: #007bff;
border-radius: 5px;
transition: width 0.3s ease;
}
.file-upload-progress i{
position: absolute;
font-size: 20px;
color:#999;
right:-30px;
top:-10px;
}
.file-upload-progress i:hover{
color:#007bff;
}
.file-upload-divider {
display: none;
line-height: 0;
margin: 10px 0;
padding: 0;
border: none!important;
border-bottom: 1px solid #eee!important;
clear: both;
background: 0 0;
}
.file-preview {
display: none;
margin-top: 10px;
width: 200px;
height: 200px;
overflow: hidden;
border-radius: 5px;
border: 1px solid transparent;
transition: all 0.3s ease;
}
.file-preview img {
width: 100%;
height: 100%;
object-fit: cover;
transition: all 1s ease;
}
.file-upload:hover img{
transform: scale(1.02,1.02);
}
</style>
</head>
<body>
<div class="file-upload">
<input type="file" name="avatar" class="file-upload-input" />
<div class="file-upload-icon">
<i class="fas fa-plus"></i>
</div>
<p class="file-upload-text">选择文件,或者拖拽上传</p>
<div class="file-upload-progress">
<div class="file-upload-progress-bar">
</div>
<i class="fas fa-trash-can"></i>
</div>
<hr class="file-upload-divider" />
<div class="file-preview"></div>
</div>
</body>
<script>
const progressBar = document.querySelector('.file-upload-progress-bar');
const progress = document.querySelector('.file-upload-progress');
const fileUpload = document.querySelector('.file-upload');
const fileUploadText = document.querySelector('.file-upload-text');
const divider = document.querySelector('.file-upload-divider');
const avatar = document.querySelector('.file-upload-input');
const filePreview = document.querySelector('.file-preview');
const previewImage = document.createElement('img');
const trash = document.querySelector('.fa-trash-can');
//拖拽事件
fileUpload.ondragenter = function (e) {
e.preventDefault();
fileUpload.style.backgroundColor = '#f2f2f2';
fileUpload.style.borderColor = '#007bff';
}
fileUpload.ondragleave = function (e) {
e.preventDefault();
fileUpload.style.backgroundColor = '';
fileUpload.style.borderColor = '';
}
let cancelUpload = null;
avatar.addEventListener('change', function (e) {
fileUpload.style.backgroundColor = '';
fileUpload.style.borderColor = '';
if(avatar.files.length === 0){
alert("请选择文件");
return;
}
if(avatar.files.length > 1){
alert("仅支持单文件上传");
return;
}
//前端验证
if (!validateFile(avatar.files[0])) {
return;
}
progress.style.display = 'block';
cancelUpload = upload(
avatar.files[0],
'http://localhost:3002/upload/single',
showPreview,
setProgress,
()=>{
progress.style.display = 'none';
progressBar.style.width = '0';
}
)
})
trash.onclick = ()=>{
cancelUpload && cancelUpload();
};
const showPreview = (resp) => {
fileUploadText.style.display = 'block';
divider.style.display = 'block';
filePreview.style.display = 'block';
previewImage.src = resp.data;
filePreview.appendChild(previewImage);
}
const setProgress = (percent) => {
progressBar.style.width = percent + '%';
}
const upload = (file,url,onFinish,onProgress,onloadend)=>{
let xhr = new XMLHttpRequest();
xhr.open('POST', url);
let formData = new FormData();
formData.append('avatar', file);
xhr.onload = function () {
const resp = JSON.parse(xhr.responseText);
onFinish && onFinish(resp);
}
xhr.upload.onprogress = (e) => {
// 上传进度变化时更新 UI
const percent = Math.floor((e.loaded / e.total) * 100);
onProgress && onProgress(percent);
};
xhr.onloadend = onloadend;
xhr.send(formData);
return () => {
xhr.abort();
}
}
function validateFile(file) {
const sizeLimit = 2 * 1024 * 1024;
const legalExts = ['.jpg', '.jpeg', '.gif', '.png', '.bmp', '.webp'];
if (file.size > sizeLimit) {
alert('文件尺寸过大');
return false;
}
const name = file.name.toLowerCase();
if (!legalExts.some((ext) => name.endsWith(ext))) {
alert('文件类型不正确');
return false;
}
return true;
}
</script>
</html>
上面的代码中,js相关代码,也就多了进度条和预览图片相关的代码,也没有过多的逻辑,无非就是显示隐藏效果,最多也就是躲了进度条长度的计算,其他都是html+css的效果了。
至于拖拽上传呢?好像没有拖拽的代码啊?html的input标签,本身就支持拖拽,直接拖拽到div的区域就行了,上传的部分是没有任何影响的,都是一样的。
对上传业务的封装
整个上传的业务代码应该是一套整体流程,因此我们可以将其封装为一个函数
//......其他代码省略
let cancelUpload = null;
avatar.addEventListener('change', function (e) {
fileUpload.style.backgroundColor = '';
fileUpload.style.borderColor = '';
progress.style.display = 'block';
if(avatar.files.length === 0){
alert("请选择文件");
return;
}
if(avatar.files.length > 1){
alert("仅支持单文件上传");
return;
}
cancelUpload = upload(
avatar.files[0],
'http://localhost:3002/upload/single',
setProgress,
showPreview,
()=>{
progress.style.display = 'none';
progressBar.style.width = '0';
}
)
})
trash.onclick = ()=>{
cancelUpload && cancelUpload();
};
const showPreview = (resp) => {
fileUploadText.style.display = 'block';
divider.style.display = 'block';
filePreview.style.display = 'block';
previewImage.src = resp.data;
filePreview.appendChild(previewImage);
}
const setProgress = (percent) => {
progressBar.style.width = percent + '%';
}
const upload = (file,url,onProgress,onFinish,onloadend)=>{
let xhr = new XMLHttpRequest();
xhr.open('POST', url);
let formData = new FormData();
formData.append('avatar', file);
xhr.onload = function () {
const resp = JSON.parse(xhr.responseText);
onFinish(resp);
}
xhr.upload.onprogress = (e) => {
// 上传进度变化时更新 UI
const percent = Math.floor((e.loaded / e.total) * 100);
onProgress(percent);
};
xhr.onloadend = onloadend;
xhr.send(formData);
return () => {
xhr.abort();
}
}
Comments