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