web实现文件上传的方式总结
文章目录
- 基本上传方式
- 访问文件
- 传统的 DOM 选择器访问一个已经被选择的文件
- 通过 change 事件访问被选择的文件
- 动态添加change监听器
- Ajax 上传
- 监测上传进度
- 分割上传
- 拖拽上传
- 选择类型
- 自定义样式
- 通过 click() 方法使用隐藏的 file input 元素
- 使用 label 元素来触发一个隐藏的 file input 元素
文件上传是 Web 开发常见需求,上传文件需要用到文件输入框,如果给文件输入框添加一个 multiple 属性则可以一次选择多个文件
<input multiple type="file" />
点击这个输入框就可以打开浏览文件对话框选择文件了,一般一个输入框上传一个文件就行,要上传多个文件也可以用多个输入框来处理,这样做是为了兼容那些不支持 multiple 属性的浏览器,同时用户一般也不会选择多个文件
基本上传方式
当把文件输入框放入表单中,提交表单的时候即可将选中的文件一起提交上传到服务器,需要注意的是由于提交的表单中包含文件,因此要修改一下表单元素的 enctype
属性为 multipart/form-data
<form action="#" enctype="multipart/form-data" method="post"><input name="file" type="file"><button type="submit">Upload</button>
</form>
这样上传方式是传统的同步上传,上传的文件如果很大,往往需要等待很久,上传完成后页面还会重新加载,并且必须等待上传完成后才能继续操作。目前基本不会采用这种方式,仅做了解。
访问文件
File API 提供了访问文件的能力,通过输入框的 files 属性访问,这会得到一个 FileList,这是一个集合,如果只选择了一个文件,那么集合中的第一个元素就是这个文件
传统的 DOM 选择器访问一个已经被选择的文件
const file = document.querySelector('input[type="file"]').files[0]console.log(file.name) // 文件名称
console.log(file.size) // 文件大小
console.log(file.type) // 文件类型
通过 change 事件访问被选择的文件
<input type="file" id="input" onchange="handleFiles">fileChange (e) {console.log(e.target.files)
}
动态添加change监听器
你需要使用 EventTarget.addEventListener() 去添加 change 事件监听器,像这样:
const inputElement = document.getElementById("input");
inputElement.addEventListener("change", handleFiles, false);
function handleFiles() {const fileList = this.files; /* now you can work with the file list */
}
注意在这个例子里,handleFiles() 方法本身是一个事件处理器,不像之前的例子中,它被事件处理器调用然后传递给它一个参数。
Ajax 上传
由于可以通过 File API 直接访问文件内容,再结合 XMLHttpRequest 对象直接将文件上传,将其作为参数传给 XMLHttpRequest 对象的 send 方法即可
var xhr = new XMLHttpRequest()
xhr.open('POST', '/upload/url', true)
xhr.send(file)
不过一些原因不建议直接这样传递文件,而是使用 FormData 对象来包装需要上传的文件,FormData 是一个构造函数,使用的时候先 new 一个实例,然后通过实例的 append 方法向其中添加数据,直接把需要上传的文件添加进去
var formData = new FormData()
formData.append('file', file, file.name) // 第 3 个参数是文件名称
formData.append('username', 'Mary') // 还可以添加额外的参数
甚至也可以直接把表单元素作为实例化参数,这样整个表单中的数据就全部包含进去了
var formData = new FormData(document.querySelector('form'))
数据准备好后,就是上传了,同样是作为参数传给 XMLHttpRequest 对象的 send 方法
var xhr = new XMLHttpRequest()
xhr.open('POST', '/upload/url', true) // true 该参数规定请求是否异步处理。
xhr.upload.onloadstart = function(){// 上传开始执行方法ot = new Date().getTime(); // 设置上传开始时间oloaded = 0; // 设置上传开始时,以上传的文件大小为0
};
xhr.upload.onprogress = progressFunction;//【上传进度调用方法实现】
xhr.onload = uploadComplete; // 请求完成
xhr.onerror = uploadFailed; // 请求失败
xhr.send(formData) // 开始上传,发送form数据//上传成功响应
function uploadComplete(evt) {//服务端接收完文件返回的结果var data = JSON.parse(evt.target.responseText);if(data.success) {alert("上传成功!");}else{alert("上传失败!");}
}
//上传失败
function uploadFailed(evt) {alert("上传失败!");
}
//取消上传
function cancelUploadFile(){xhr.abort();
}
监测上传进度
XMLHttpRequest 对象还提供了一个 progress 事件,基于这个事件可以知道上传进度如何
var xhr = new XMLHttpRequest()
xhr.open('POST', '/upload/url', true)
xhr.upload.onprogress = progressHandler // 这个函数接下来定义
上传的 progress 事件由 xhr.upload 对象触发,在事件处理程序中使用这个事件对象的 loaded(已上传字节数) 和 total(总数) 属性来计算上传的进度
function progressHandler(e) {var percent = Math.round((e.loaded / e.total) * 100)
}
上面的计算会得到一个表示完成百分比的数字,不过这两个值也不一定总会有,保险一点先判断一下事件对象的 lengthComputable 属性
function progressHandler(e) {if (e.lengthComputable) {var percent = Math.round((e.loaded / e.total) * 100)}
}
上传进度详细实现
<progress id="progressBar" value="0" max="100" style="width: 300px;"></progress>
<span id="percentage"></span><span id="time"></span>
//上传进度实现方法,上传过程中会频繁调用该方法
function progressFunction(evt) {var progressBar = document.getElementById("progressBar");var percentageDiv = document.getElementById("percentage");// event.total是需要传输的总字节,event.loaded是已经传输的字节。如果event.lengthComputable不为真,则event.total等于0if (evt.lengthComputable) {//progressBar.max = evt.total;progressBar.value = evt.loaded;percentageDiv.innerHTML = Math.round(evt.loaded / evt.total * 100) + "%";}var time = document.getElementById("time");var nt = new Date().getTime();//获取当前时间var pertime = (nt-ot)/1000; //计算出上次调用该方法时到现在的时间差,单位为sot = new Date().getTime(); //重新赋值时间,用于下次计算var perload = evt.loaded - oloaded; //计算该分段上传的文件大小,单位boloaded = evt.loaded;//重新赋值已上传文件大小,用以下次计算//上传速度计算var speed = perload/pertime;//单位b/svar bspeed = speed;var units = 'b/s';//单位名称if(speed/1024>1){speed = speed/1024;units = 'k/s';}if(speed/1024>1){speed = speed/1024;units = 'M/s';}speed = speed.toFixed(1);//剩余时间var resttime = ((evt.total-evt.loaded)/bspeed).toFixed(1);time.innerHTML = ',速度:'+speed+units+',剩余时间:'+resttime+'s';if(bspeed==0) time.innerHTML = '上传已取消';
}
分割上传
使用文件对象的 slice 方法可以分割文件,给该方法传递两个参数,一个起始位置和一个结束位置,这会返回一个新的 Blob 对象,包含原文件从起始位置到结束位置的那一部分(文件 File 对象其实也是 Blob 对象,这可以通过 file instanceof Blob 确定,Blob 是 File 的父类)
var blob = file.slice(0, 1024) // 文件从字节位置 0 到字节位置 1024 那 1KB
将文件分割成几个 Blob 对象分别上传就能实现将大文件分割上传
function upload(file) {let formData = new FormData()formData.append('file', file)let xhr = new XMLHttpRequest()xhr.open('POST', '/upload/url', true)xhr.send(formData)
}var blob = file.slice(0, 1024)
upload(blob) // 上传第一部分var blob2 = file.slice(1024, 2048)
upload(blob2) // 上传第二部分// 上传剩余部分
通常用一个循环来处理更方便
var pos = 0 // 起始位置
var size = 1024 // 块的大小while (pos < file.size) {let blob = file.slice(pos, pos + size) // 结束位置 = 起始位置 + 块大小upload(blob)pos += size // 下次从结束位置开始继续分割
}
服务器接收到分块文件进行重新组装的代码就不在这里展示了
使用这种方式上传文件会一次性发送多个 HTTP 请求,那么如何处理这种多个请求同时发送的情况呢?方法有很多,可以用 Promise 来处理,让每次上传都返回一个 promise 对象,然后用 Promise.all 方法来合并处理,Promise.all 方法接受一个数组作为参数,因此将每次上传返回的 promise 对象放在一个数组中
var promises = []while (pos < file.size) {let blob = file.slice(pos, pos + size)promises.push(upload(blob)) // upload 应该返回一个 promisepos += size
}
同时改造一下 upload 函数使其返回一个 promise
function upload(file) {return new Promise((resolve, reject) => {let formData = new FormData()formData.append('file', file)let xhr = new XMLHttpRequest()xhr.open('POST', '/upload/url', true)xhr.onload = () => resolve(xhr.responseText)xhr.onerror = () => reject(xhr.statusText)xhr.send(formData)})
}
当一切完成后
Promise.all(promises).then((response) => {console.log('Upload success!')
}).catch((err) => {console.log(err)
})
支持文件分割的浏览器可以参考 caniuse
判断一下文件对象是否有该方法就能知道浏览器是否支持该方法,对于早期的部分版本浏览器需要加上对应的浏览器厂商前缀
var slice = file.slice || file.webkitSlice || file.mozSliceif (slice) {let blob = slice.call(file, 0, 1024) // callupload(blob)
} else {upload(file) // 不支持分割就只能直接上传整个文件了,或者提示文件过大
}
拖拽上传
你还可以让用户将文件拖拽到你的网页应用中。
第一步是创建一个drop区域。虽然你网页内容的哪部分接受拖放取决于你的应用设计,但是使一个元素接收drop事件是很容易的。
let dropbox;dropbox = document.getElementById("dropbox");
dropbox.addEventListener("dragenter", dragenter, false);
dropbox.addEventListener("dragover", dragover, false);
dropbox.addEventListener("drop", drop, false);
在这个例子中,我们将ID为dropbox的元素变为了我们的drop区域。这是通过给元素添加dragenter (en-US), dragover (en-US), 和drop (en-US) 事件监听器实现的。
我们其实并不需要对dragenter and dragover 事件进行处理,所以这些函数都很简单。他们只需要包括禁止事件传播和阻止默认事件:
function dragenter(e) {e.stopPropagation();e.preventDefault();
}function dragover(e) {e.stopPropagation();e.preventDefault();
}
真正的奥妙在drop()这个函数中:
function drop(e) {e.stopPropagation();e.preventDefault();var dt = e.dataTransfer;var files = dt.files;handleFiles(files);
}
这里,我们从事件中获取到了dataTransfer 这个域,然后从中得到文件列表,再将它们传递给handleFiles()函数。在这之后,处理文件的方法和用input元素或者用拖拽就是一样的了。
选择类型
给文件输入框添加 accept 属性即可指定选择文件的类型,比如要选择 png 格式的图片,则指定其值为 image/png,如果要允许选择所有类型的图片,就是 image/*
<input accept="image/*" type="file">
添加 capture 属性可以调用设备机能,比如 capture=“camera” 可以调用相机拍照,不过这并不是一个标准属性,不同设备实现方式也不一样,需要注意
经测 iOS 设备添加该属性后只能拍照而不能从相册选择文件了,所以判断一下
if (iOS) { // iOS 用 navigator.userAgent 判断input.removeAttribute('capture')
}
不支持的浏览器会自动忽略这些属性
自定义样式
通过 click() 方法使用隐藏的 file input 元素
你可以隐藏公认难看的 file 元素并显示你自己的界面来打开文件选择器,然后显示哪个或哪些文件被用户选中了。你可以通过给 input 元素添加 display:none 的样式,再调用 元素的 click() 方法来实现。
考虑这段HTML:
<input type="file" id="fileElem" multiple accept="image/*" style="display:none" onchange="handleFiles(this.files)">
<button id="fileSelect">Select some files</button>
处理 click 事件的代码类似于这样:
const fileSelect = document.getElementById("fileSelect"),fileElem = document.getElementById("fileElem");fileSelect.addEventListener("click", function (e) {if (fileElem) {fileElem.click();}
}, false);
你可以给这个用来打开文件选择器的新按钮添加任何你想要的样式。
使用 label 元素来触发一个隐藏的 file input 元素
允许在不使用 JavaScript(click() 方法)来打开文件选择器,可以使用 <label>
元素。注意在这种情况下,input 元素不能使用 display: none(或 visibility: hidden)隐藏,否则 label 将无法通过键盘访问。使用 visually-hidden technique 作为替代。
考虑这段HTML:
<input type="file" id="fileElem" multiple accept="image/*" class="visually-hidden">
<label for="fileElem">Select some files</label>
和这段CSS:
.visually-hidden {position: absolute !important;height: 1px;width: 1px;overflow: hidden;clip: rect(1px, 1px, 1px, 1px);
}/* Separate rule for compatibility, :focus-within is required on modern Firefox and Chrome */
input.visually-hidden:focus + label {outline: thin dotted;
}
input.visually-hidden:focus-within + label {outline: thin dotted;
}
这里不需要添加任何 JavaScript 代码来调用fileElem.click(),另外,这时你也可以给 label 元素添加你想要的样式。您需要在其 label 上提供隐藏 input 字段的焦点状态的视觉提示,比如上面用的轮廓,或者背景颜色或边框阴影。(截至编写时为止,Firefox 不显示 元素的视觉提示。)
web实现文件上传的方式总结相关推荐
- java web 文件上传工具类_JavaWeb中实现文件上传的方式有哪些?
上回我们说了下文件下载的方式有哪些,这次我们从不同的环境下简单来说说文件上传的方式有哪些. 文件上传的方式Servlet2.5 方式 Servlet3.0 方式 SpringMVC 方式 案例实操 S ...
- 2020小迪培训(第20天WEB 漏洞-文件上传之基础及过滤方式)
WEB 漏洞-文件上传之基础及过滤方式 前言 知识点 什么是文件上传漏洞? 有文件上传不一定存在漏洞 凡是存在文件上传的地方/功能的地方都可以进行文件上传漏洞测试 上传文件操作的代码的完整性.安全性, ...
- tomcat temp 大量 upload 文件_问题:JavaWeb中实现文件上传的方式有哪些?
问题:JavaWeb中实现文件上传的方式有哪些? 上回我们说了下文件下载的方式有哪些,这次我们从不同的环境下简单来说说文件上传的方式有哪些. 文件上传的方式 Servlet2.5 方式 Servlet ...
- java mime上传_JavaWeb中实现文件上传的方式有哪些?
上回我们说了下文件下载的方式有哪些,这次我们从不同的环境下简单来说说文件上传的方式有哪些. 文件上传的方式Servlet2.5 方式 Servlet3.0 方式 SpringMVC 方式 案例实操 S ...
- 2020小迪培训(第21天 WEB 漏洞-文件上传之后端黑白名单绕过)
WEB 漏洞-文件上传之后端黑白名单绕过 前言 文件上传常见验证 后缀名,类型,文件头等 后缀名:黑名单,白名单 黑名单:明确不允许上传的格式后缀 asp php jsp cgi war- 缺陷:在定 ...
- [ctfshow]web入门——文件上传(web156-web163)
[ctfshow]web入门--文件上传(web156-web163) [ctfshow]web入门--文件上传 [ctfshow]web入门--文件上传(web156-web163) web156 ...
- WEB漏洞-文件上传之后端黑白名单绕过
WEB漏洞-文件上传之后端黑白名单绕过 文件上传常见验证:后缀名,类型,文件头 后缀名:黑名单,白名单 黑名单:asp php jsp aspx cgi war- 白名单: txt jpg zip r ...
- CTFshow——web入门——文件上传
web入门-文件上传 web151 web152 web153 web154 web155 web156 web157 web158 web159 web160 web161 web162.web16 ...
- 第20天-WEB漏洞-文件上传之基础及过滤方式
思维导图 文件上传漏洞 1-什么是文件上传漏洞? 凡是存在文件上传的地方均有可能存在文件上传漏洞,关于上传文件操作的时候对方代码写的是否完整.是否安全,一旦疏忽了某个地方可能会造成文件上传漏洞. 2- ...
最新文章
- 用MFC制作程序启动logo
- php 判断是否文件,利用PHP判断文件是否为图片的方法总结
- Git之submodule使用总结
- 建立完善的日期定义表
- mysql order 中文版,MySQL Order By排序结果
- java移位操作示例
- Presto Facebook 开源的大数据查询引擎
- DepthMap(1):D. Eigen (NIPS2014)
- 题解:100元买100只鸡,公鸡4元一只,母鸡3元一只,小鸡1元3只,问公鸡,母鸡,小鸡各买了多少只?
- Unity 5.3 官方VR教程(—)VR综述
- 卡内基梅隆大学计算机科学博士,美国卡内基梅隆大学博士需要几年
- Docker核心技术(一):镜像与容器
- JavaScript生成图形验证码
- java获取double类型区间随机数
- 网络安全体系与网络安全模型
- 树莓派部署BT下载机
- 一天一夜chromebook折腾心得
- 关于mybatis的分页实现
- Android 6.0权限请求相关及权限分组
- Learn to Augment: Joint Data Augmentation and Network Optimizationfor Text Recognition