点击这个输入框就可以打开浏览文件对话框选择文件了,一般一个输入框上传一个文件就行,要上传多个文件也可以用多个输入框来处理,这样做"/>

用于web开发的文件上传的多种实现

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

推荐阅读