node.js 实战——在express 中将input file 美化,并完成裁剪、上传进度条
美化上传按钮
在ejs 页面
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"></meta><title><%= title %></title><link rel='stylesheet' href='/stylesheets/form.css'/><!-- 本地 Bootstrap 引入方式 --><link href="/bootstrap/css/bootstrap.min.css" rel="stylesheet"><script src="/bootstrap/js/bootstrap.bundle.min.js"></script><script src="./javascripts/image-upload.js"></script>
</head>
<body>
<div class="container"><h1>文件上传</h1><from action="/upload-img" method="post" enctype="multipart/form-data"><label class="upload-container"><span class="upload-icon" id="uploadIcon">+</span><input accept="image/*" id="avatarInput" type="file" /><img id="avatarPreview" class="preview-img d-done" /><button type="button" class="remove-btn" id="removeBtn">x</button></label><button type="submit" class="btn btn-primary mt-3">提交</button></from>
</div>
</body>
</html>
样式
.upload-container{width: 150px;height: 150px;border: 2px solid #ccc;border-radius: 10px;display: flex;align-items: center;justify-content: center;cursor: pointer;position: relative;overflow: hidden;background-color: #f8f9fa;transition:border-color 0.3s;.upload-icon{font-size:2rem;color:#999;//禁用鼠标事件,该元素变得“不可点击”、“鼠标穿透”pointer-events: none; //用来控制一个元素是否能响应鼠标事件(点击、悬停、拖动等)}.preview-img{width:100%;height: 100%;object-fit: cover;position: absolute;top: 0;left: 0;}.remove-btn{position: absolute;top:5px;right:5px;background-color:rgba(0,0,0,.6);color:#ffffff;border:none;border-radius:50%;width:24px;height:24px;display:none;align-items: center;justify-content: center;cursor:pointer;font-weight: bold;}
}.upload-container:hover{border-color: #007bff;
}.upload-container input[type="file"]{position: absolute;opacity: 0;height: 100%;width: 100%;cursor: pointer;
}
完成效果
这个效果没有裁剪,进度条、多图片上传
上传进度条
<div class="progress mt-2 d-done" id="progressWrapper"><div class="progress-bar" role="progressbar" id="uploadProgress" style="width: 0%">0%</div></div>
document.getElementById('uploadForm').addEventListener('submit', function(e){e.preventDefault();// const blob =preview._blob;// if(!blob){// alert("请先选择并裁剪头像");// return;// }const file =input.files[0];if(!file) return alert('Please upload a valid image!');let formData = new FormData();formData.append('file', file);let xhr = new XMLHttpRequest();xhr.open('POST', '/upload-avatar');xhr.upload.addEventListener('progress', (e) => {if(e.lengthComputable){let percent = Math.round((e.loaded / e.total) * 100);progressWrapper.classList.remove('d-none');progressBar.style.width= percent + '%';progressBar.innerText = percent + '%';}})xhr.onload =function () {if(xhr.status === 200) {alert("successfully uploaded!");}else{alert("fail to upload");}}xhr.send(formData);})
使用cropperjs 裁剪图片
使用npm安装
npm install cropperjs@1.5.13
❗ 关键点:cropperjs@2.x 为模块化版本,不再提供 dist/ 下的浏览器用 JS/CSS 文件
从 v2.0.0 开始,cropperjs 改为 纯 ESM(ES Module)包,它不再包含:
- cropper.js
- cropper.css
- dist/ 文件夹
📌 小提示
版本 | 适合人群 | 是否自带 dist/ |
---|---|---|
1.5.13 | 普通浏览器/EJS 项目 | ✅ 有 JS/CSS,推荐 |
2.x | Vite/Webpack 打包项目 | ❌ 无,需构建 |
引入js、css
<!--本地开发引入 cropper --><link rel="stylesheet" href="/cropper/cropper.css"><script src="/cropper/cropper.js"></script>
弹框
<!-- 裁剪模态框--><div class="modal fade modal-mask" id="cropModal" tabindex="1" aria-hidden="true"><div class="modal-dialog modal-dialog-centered modal-fullscreen"><div class="modal-content p-3"><div class="modal-body" style="width: 100%; height: 600px;"><img id="cropImg" /></div><div class="modal-footer"><button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button><button type="button" class="btn btn-success" id="cropConfirm">确认裁剪</button></div></div></div></div>
js
input.addEventListener('change', (e)=> {let that=e.target;let file=that.files[0];if(file && file.type.startsWith('image/')) {let reader=new FileReader();reader.onloadend = (e) => {//未裁剪前使用的代码// preview.src = e.target.result;// preview.classList.remove('d-none');// removeBtn.style.display = 'flex';// uploadIcon.style.display = 'none';//裁剪时的代码cropImage.src = e.target.result;new bootstrap.Modal(document.getElementById('cropModal')).show();cropImage.onload =()=>{if(cropper) cropper.destroy();cropper =new Cropper(cropImage,{aspectRatio: 1,autoCropArea:1,viewMode:2})}}reader.readAsDataURL(file);}})