一个以英文句号(“.”)开头的合法的不区分大小写的文件名扩展名。例如:.jpg
、.pdf
或 .doc
。
一个不带扩展名的 MIME 类型字符串。
字符串 audio/*
,表示“任何音频文件”。
字符串 video/*
,表示“任何视频文件”。
字符串 image/*
,表示“任何图片文件”。
accept 属性的值是包含一个或多个(用逗号分隔)唯一文件类型说明符的字符串。例如,一个文件选择器需要能被表示成一张图片的内容,包括标准的图片格式和 PDF 文件,大概是这样的:
可以用 accept 属性指定可接受的文件类型,它是一个以逗号间隔的文件扩展名和 MIME 类型列表。一些例子如下所示:
image/*
MIME 类型的文件。(许多移动设备也允许用户在使用它时用摄像头拍照。)capture
属性是一个字符串,如果 accept
属性指出了 input
是图片或者视频类型,则它指定了使用哪个摄像头去获取这些数据。值 user
表示应该使用前置摄像头和(或)麦克风。值 environment
表示应该使用后置摄像头和(或)麦克风。如果缺少此属性,则用户代理可以自由决定做什么。如果请求的前置模式不可用,则用户代理可能退回到其首选的默认模式。
不支持的浏览器会自动忽略这些属性
在实际开发中,安卓与苹果表现不一致,更多使用APP封装的调用方法,类似微信js-sdk方案。
//只调用相机
//只调用摄像机
//只调用录音设备,苹果表现依然为调用摄像机 //调用相机与文件相册
//调用摄像机与文件视频
//调用录音设备与文件录音
如果给文件输入框添加一个 multiple
属性则可以一次选择多个文件
input
元素添加 display:none
的样式,
元素的 click()
方法来实现,file
元素的change事件,获取上传的文件信息。
选择文件
JavaScript(click() 方法)
来打开文件选择器,可以使用
元素。display: none
(或 visibility: hidden
)隐藏,否则 label 将无法通过键盘访问。
这里不需要添加任何 JavaScript 代码来调用fileElem.click(),另外,这时你也可以给 label 元素添加你想要的样式。您需要在其 label 上提供隐藏 input 字段的焦点状态的视觉提示,比如上面用的轮廓,或者背景颜色或边框阴影。
当把文件输入框放入表单中,提交表单的时候即可将选中的文件一起提交上传到服务器,需要注意的是由于提交的表单中包含文件,因此要修改一下表单元素的 enctype
属性为 multipart/form-data
这是传统的上传方式,且无法自定义请求header
中的参数(如token信息),目前基本不会采用这种方式进行,仅做了解。
File API 提供了访问文件的能力,files
属性访问,这会得到一个 FileList
,这是一个集合,如果只选择了一个文件,那么集合中的第一个元素就是这个文件
const file = document.querySelector('input[type="file"]').files[0]console.log(file.name) // 文件名称
console.log(file.size) // 文件大小
console.log(file.type) // 文件类型
function handleFiles (files) {console.log(files)
}
你需要使用 EventTarget.addEventListener()
去添加 change 事件监听器,像这样:
const inputElement = document.getElementById("input");
inputElement.addEventListener("change", handleFiles, false);
function handleFiles() {const fileList = this.files;
}
注意在这个例子里,handleFiles() 方法本身是一个事件处理器,不像之前的例子中,它被事件处理器调用然后传递给它一个参数。
由于可以通过 File API 直接访问文件内容,再结合 XMLHttpRequest
对象直接将文件上传,将其作为参数传给 XMLHttpRequest
对象的 send
方法即可。
const xhr = new XMLHttpRequest()
xhr.open('POST', '/upload/url', true)
xhr.send(file)
不过一些原因不建议直接这样传递文件,而是使用 FormData
对象来包装需要上传的文件,FormData
是一个构造函数,使用的时候先 new
一个实例,然后通过实例的 append
方法向其中添加数据,直接把需要上传的文件添加进去
const formData = new FormData()
formData.append('file', file, file.name) // 第 3 个参数是文件名称
formData.append('username', 'Mary') // 还可以添加额外的参数
数据准备好后,就是上传了,同样是作为参数传给 XMLHttpRequest
对象的 send
方法
function submit() {// 表单数据const formData = new FormData()const file = document.querySelector('#fileInput').files[0]formData.append('file', file, file.name) // 第 3 个参数是文件名称formData.append('appKey', 'xxx')formData.append('appSecret', 'xxx')const xhr = new XMLHttpRequest()// 上传成功响应function uploadComplete(evt) {const data = JSON.parse(evt.target.responseText);if (data.code === '200') {console.log('上传成功!');} else {console.log('上传失败!');}}// 上传失败function uploadFailed() {console.log('上传失败!');}// 取消上传function cancelUploadFile() {xhr.abort();console.log('取消上传')}xhr.open('POST', 'http://www.xxx.com/url', true) // true 该参数规定请求是否异步处理。xhr.onload = uploadComplete; // 请求完成xhr.onerror = uploadFailed; // 请求失败xhr.send(formData) // 开始上传,发送form数据setTimeout(cancelUploadFile, 5000); // 5秒后,模拟取消上传操作
}
XMLHttpRequest
对象还提供了一个 progress
事件,基于这个事件可以知道上传进度如何
const xhr = new XMLHttpRequest()
xhr.open('POST', '/upload/url', true)
xhr.upload.onprogress = progressHandler // 这个函数接下来定义
上传的 progress 事件由 xhr.upload
对象触发,在事件处理程序中使用这个事件对象的 loaded
(已上传字节数) 和 total
(总数) 属性来计算上传的进度
function progressHandler(e) {const percent = Math.round((e.loaded / e.total) * 100)
}
上面的计算会得到一个表示完成百分比的数字,不过这两个值也不一定总会有,保险一点先判断一下事件对象的 lengthComputable
属性
function progressHandler(e) {if (e.lengthComputable) {const percent = Math.round((e.loaded / e.total) * 100)}
}
上传进度详细实现
function submit() {let oldTime = 0 // 上一次时间let oldLoaded = 0 // 上一次加载数据字节大小const progressBarDom = document.querySelector('#progressBar')const percentageDom = document.querySelector('#percentage')const speedDom = document.querySelector('#speed')const remainTimeDom = document.querySelector('#remainTime')function onprogress(evt) {const nowTime = new Date().getTime() // 当前时间const perTime = (nowTime - oldTime) / 1000 // 函数调用间隔时间,单位为soldTime = nowTime // 重新赋值,待下次计算// event.total是需要传输的总字节,event.loaded是已经传输的字节。如果event.lengthComputable不为真,则event.total等于0const perLoad = evt.loaded - oldLoaded // 函数调用间隔时间,新载入的字节大小oldLoaded = evt.loaded // 重新赋值,待下次计算let speed = perLoad / perTime // 原始速率,b/sconst originSpeed = speed // 不参与后续速率计算,b/slet unit = 'b/s' // 速率单位if (speed / 1024 > 1) {speed /= 1024unit = 'k/s'}if (speed / 1024 > 1) {speed /= 1024unit = 'M/s'}if (evt.lengthComputable) {const loadPercent = (evt.loaded / evt.total) * 100progressBarDom.value = Math.round(loadPercent) // 进度条百分比percentageDom.innerText = `${(loadPercent).toFixed(1)}%` // 进度显示百分比speedDom.innerText = speed.toFixed(1) + unit // 上传速率remainTimeDom.innerText = `,还剩${((evt.total - evt.loaded) / originSpeed).toFixed()}秒` // 剩余时间if (originSpeed === 0) remainTimeDom.innerHTML = '上传已取消'}}const formData = new FormData()formData.append('appKey', 'xxx')formData.append('appSecret', 'xxx')formData.append('file', document.querySelector('#fileInput').files[0])const xhr = new XMLHttpRequest()xhr.open('post', 'http://wwww.xxx.com/url', true)xhr.upload.onprogress = onprogressxhr.send(formData)
}
使用文件对象的 slice
方法可以分割文件,给该方法传递两个参数,一个起始位置和一个结束位置,这会返回一个新的 Blob
对象,包含原文件从起始位置到结束位置的那一部分(文件 File
对象其实也是 Blob
对象,这可以通过 new File(['name'], 'testFile') instanceof Blob
确定,Blob
是 File
的父类)
const blob = file.slice(0, 1024 * 1024) // 文件从字节位置 0 到字节位置 1024*1024 即 1M
将文件分割成几个 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)
}let pos = 0 // 起始位置
const size = 1024*1024 // 块的大小,即1M// 通常用一个循环来处理更方便
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', 'http://www.xxx.com/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)})
经实际测试,将大文件分割上传,可极大缩短总上传时间
你还可以让用户将文件拖拽到你的网页应用中。
第一步是创建一个drop区域。虽然你网页内容的哪部分接受拖放取决于你的应用设计,但是使一个元素接收drop事件是很容易的。
const dropbox = document.getElementById("dropbox");
dropbox.addEventListener("dragenter", dragenter, false);
dropbox.addEventListener("dragover", dragover, false);
dropbox.addEventListener("drop", drop, false);
在这个例子中,我们将id为dropbox的元素变为了我们的drop区域。这是通过给元素添加dragenter
dragover
, 和drop
事件监听器实现的。
我们其实并不需要对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()函数。在这之后,处理文件的方法与之前一致。