B站看到了渡一大师课的切片,自己实现了一下,做下记录
效果展示
分为上传前、上传中和上传后
实现
分为两步
- 界面交互
- 网络请求
源码如下
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;
}