web开发之文件上传的多种实现方式(附代码)
时间:2022-02-11 16:22
文件上传是 Web 开发常见需求,上传文件需要用到文件输入框,如果给文件输入框添加一个 multiple 属性则可以一次选择多个文件(不支持的浏览器会自动忽略这个属性) 点击这个输入框就可以打开浏览文件对话框选择文件了,一般一个输入框上传一个文件就行,要上传多个文件也可以用多个输入框来处理,这样做是为了兼容那些不支持 multiple 属性的浏览器,同时用户一般也不会选择多个文件 (推荐学习:HTML视频教程 ) 基本上传方式 当把文件输入框放入表单中,提交表单的时候即可将选中的文件一起提交上传到服务器,需要注意的是由于提交的表单中包含文件,因此要修改一下表单元素的 enctype 属性为 multipart/form-data 这样上传方式是传统的同步上传,上传的文件如果很大,往往需要等待很久,上传完成后页面还会重新加载,并且必须等待上传完成后才能继续操作 早期的浏览器并不支持异步上传,不过可以使用 iframe 来模拟,在页面中隐藏一个 <iframe> 元素,指定一个 name 值,同时将 <form> 元素的 target 属性值指定为 <iframe> 元素的 name 属性的值,将两者关联起来 这样在提交表单上传的时候,页面就不会重新加载了,取而代之的是 iframe 重新加载了,不过 iframe 原本就是隐藏的,即使重新加载也不会感知到 访问文件 File API 提供了访问文件的能力,通过输入框的 files 属性访问,这会得到一个 FileList,这是一个集合,如果只选择了一个文件,那么集合中的第一个元素就是这个文件 支持 File API 的浏览器可以参考 caniuse Ajax 上传 由于可以通过 File API 直接访问文件内容,再结合 XMLHttpRequest 对象直接将文件上传,将其作为参数传给 XMLHttpRequest 对象的 send 方法即可 不过一些原因不建议直接这样传递文件,而是使用 FormData 对象来包装需要上传的文件,FormData 是一个构造函数,使用的时候先 new 一个实例,然后通过实例的 append 方法向其中添加数据,直接把需要上传的文件添加进去 甚至也可以直接把表单元素作为实例化参数,这样整个表单中的数据就全部包含进去了 数据准备好后,就是上传了,同样是作为参数传给 XMLHttpRequest 对象的 send 方法 XMLHttpRequest 对象还提供了一个 progress 事件,基于这个事件可以知道上传进度如何 上传的 progress 事件由 xhr.upload 对象触发,在事件处理程序中使用这个事件对象的 loaded(已上传字节数) 和 total(总数) 属性来计算上传的进度 上面的计算会得到一个表示完成百分比的数字,不过这两个值也不一定总会有,保险一点先判断一下事件对象的 lengthComputable 属性 支持 Ajax 上传的浏览器可以参考 caniuse https://caniuse.com/#feat=xhr2 分割上传 使用文件对象的 slice 方法可以分割文件,给该方法传递两个参数,一个起始位置和一个结束位置,这会返回一个新的 Blob 对象,包含原文件从起始位置到结束位置的那一部分(文件 File 对象其实也是 Blob 对象,这可以通过 file instanceof Blob 确定,Blob 是 File 的父类) 将文件分割成几个 Blob 对象分别上传就能实现将大文件分割上传 通常用一个循环来处理更方便 服务器接收到分块文件进行重新组装的代码就不在这里展示了 使用这种方式上传文件会一次性发送多个 HTTP 请求,那么如何处理这种多个请求同时发送的情况呢?方法有很多,可以用 Promise 来处理,让每次上传都返回一个 promise 对象,然后用 Promise.all 方法来合并处理,Promise.all 方法接受一个数组作为参数,因此将每次上传返回的 promise 对象放在一个数组中 同时改造一下 upload 函数使其返回一个 promise 当一切完成后 支持文件分割的浏览器可以参考 caniuse 判断一下文件对象是否有该方法就能知道浏览器是否支持该方法,对于早期的部分版本浏览器需要加上对应的浏览器厂商前缀 拖拽上传 通过拖拽 API 可以实现拖拽文件上传,默认情况下,拖拽一个文件到浏览器中,浏览器会尝试打开这个文件,要使用拖拽功能需要阻止这个默认行为 任意指定一个元素来作为释放拖拽的区域,给一个元素绑定 drop 事件 通过该事件对象的 dataTransfer 属性获取文件,然后上传即可 选择类型 给文件输入框添加 accept 属性即可指定选择文件的类型,比如要选择 png 格式的图片,则指定其值为 image/png,如果要允许选择所有类型的图片,就是 image/* 添加 capture 属性可以调用设备机能,比如 capture="camera" 可以调用相机拍照,不过这并不是一个标准属性,不同设备实现方式也不一样,需要注意 经测 iOS 设备添加该属性后只能拍照而不能从相册选择文件了,所以判断一下 不支持的浏览器会自动忽略这些属性 自定义样式 文件输入框在各个浏览器中呈现的样子都不大相同,而且给 input 定义样式也不是那么方便,如果有需要应用自定义样式,有一个技巧,可以用一个 label 关联到这个文件输入框,当点击这个 label 元素的时候就会触发文件输入框的点击,打开浏览文件的对话框,相当于点击了文件输入框一样的效果 这时就可以将原本的文件输入框隐藏了,然后给 label 元素任意地应用样式,毕竟要给 label 元素应用样式比 input 方便得多 本文来自PHP中文网,html教程栏目,欢迎学习 以上就是web开发之文件上传的多种实现方式(附代码)的详细内容,更多请关注gxlsystem.com其它相关文章!<input multiple type="file">
<form action="#" enctype="multipart/form-data" method="post">
<input name="file" type="file">
<button type="submit">Upload</button>
</form>
<form action="#" enctype="multipart/form-data" method="post" target="upload-frame">
<input name="file" type="file">
<button type="submit">Upload</button>
</form>
<iframe id="upload-frame" name="upload-frame" src="about:blank" style="display: none;"></iframe>
var input = document.querySelector('input[type="file"]')
var file = input.files[0]
console.log(file.name) // 文件名称
console.log(file.size) // 文件大小
console.log(file.type) // 文件类型
var xhr = new XMLHttpRequest()
xhr.open('POST', '/upload/url', true)
xhr.send(file)
var formData = new FormData()
formData.append('file', file, file.name) // 第 3 个参数是文件名称
formData.append('username', 'Mary') // 还可以添加额外的参数
var formData = new FormData(document.querySelector('form'))
var xhr = new XMLHttpRequest()
xhr.open('POST', '/upload/url', true)
xhr.send(formData)
监测上传进度
var xhr = new XMLHttpRequest()
xhr.open('POST', '/upload/url', true)
xhr.upload.onprogress = progressHandler // 这个函数接下来定义
function progressHandler(e) {
var percent = Math.round((e.loaded / e.total) * 100)
}
function progressHandler(e) {
if (e.lengthComputable) {
var percent = Math.round((e.loaded / e.total) * 100)
}
}
var blob = file.slice(0, 1024) // 文件从字节位置 0 到字节位置 1024 那 1KB
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 // 下次从结束位置开始继续分割
}
var promises = []
while (pos < file.size) {
let blob = file.slice(pos, pos + size)
promises.push(upload(blob)) // upload 应该返回一个 promise
pos += size
}
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)
})
var slice = file.slice || file.webkitSlice || file.mozSlice
if (slice) {
let blob = slice.call(file, 0, 1024) // call
upload(blob)
} else {
upload(file) // 不支持分割就只能直接上传整个文件了,或者提示文件过大
}
document.addEventListener('dragover', function(e) {
e.preventDefault()
e.stopPropagation()
})
var element = document.querySelector('label')
element.addEventListener('drop', function(e) {
e.preventDefault()
e.stopPropagation()
// ...
})
var file = e.dataTransfer.files[0]
upload(file) // upload 函数前面已经定义
<input accept="image/*" type="file">
<input accept="image/*" capture="camera" type="file">
if (iOS) { // iOS 用 navigator.userAgent 判断
input.removeAttribute('capture')
}
<label for="file-input"></label>
<input id="file-input" style="clip: rect(0,0,0,0); position: absolute;" type="file">