文章目录

  • 基本上传方式
  • 访问文件
    • 传统的 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实现文件上传的方式总结相关推荐

  1. java web 文件上传工具类_JavaWeb中实现文件上传的方式有哪些?

    上回我们说了下文件下载的方式有哪些,这次我们从不同的环境下简单来说说文件上传的方式有哪些. 文件上传的方式Servlet2.5 方式 Servlet3.0 方式 SpringMVC 方式 案例实操 S ...

  2. 2020小迪培训(第20天WEB 漏洞-文件上传之基础及过滤方式)

    WEB 漏洞-文件上传之基础及过滤方式 前言 知识点 什么是文件上传漏洞? 有文件上传不一定存在漏洞 凡是存在文件上传的地方/功能的地方都可以进行文件上传漏洞测试 上传文件操作的代码的完整性.安全性, ...

  3. tomcat temp 大量 upload 文件_问题:JavaWeb中实现文件上传的方式有哪些?

    问题:JavaWeb中实现文件上传的方式有哪些? 上回我们说了下文件下载的方式有哪些,这次我们从不同的环境下简单来说说文件上传的方式有哪些. 文件上传的方式 Servlet2.5 方式 Servlet ...

  4. java mime上传_JavaWeb中实现文件上传的方式有哪些?

    上回我们说了下文件下载的方式有哪些,这次我们从不同的环境下简单来说说文件上传的方式有哪些. 文件上传的方式Servlet2.5 方式 Servlet3.0 方式 SpringMVC 方式 案例实操 S ...

  5. 2020小迪培训(第21天 WEB 漏洞-文件上传之后端黑白名单绕过)

    WEB 漏洞-文件上传之后端黑白名单绕过 前言 文件上传常见验证 后缀名,类型,文件头等 后缀名:黑名单,白名单 黑名单:明确不允许上传的格式后缀 asp php jsp cgi war- 缺陷:在定 ...

  6. [ctfshow]web入门——文件上传(web156-web163)

    [ctfshow]web入门--文件上传(web156-web163) [ctfshow]web入门--文件上传 [ctfshow]web入门--文件上传(web156-web163) web156 ...

  7. WEB漏洞-文件上传之后端黑白名单绕过

    WEB漏洞-文件上传之后端黑白名单绕过 文件上传常见验证:后缀名,类型,文件头 后缀名:黑名单,白名单 黑名单:asp php jsp aspx cgi war- 白名单: txt jpg zip r ...

  8. CTFshow——web入门——文件上传

    web入门-文件上传 web151 web152 web153 web154 web155 web156 web157 web158 web159 web160 web161 web162.web16 ...

  9. 第20天-WEB漏洞-文件上传之基础及过滤方式

    思维导图 文件上传漏洞 1-什么是文件上传漏洞? 凡是存在文件上传的地方均有可能存在文件上传漏洞,关于上传文件操作的时候对方代码写的是否完整.是否安全,一旦疏忽了某个地方可能会造成文件上传漏洞. 2- ...

最新文章

  1. 用MFC制作程序启动logo
  2. php 判断是否文件,利用PHP判断文件是否为图片的方法总结
  3. Git之submodule使用总结
  4. 建立完善的日期定义表
  5. mysql order 中文版,MySQL Order By排序结果
  6. java移位操作示例
  7. Presto Facebook 开源的大数据查询引擎
  8. DepthMap(1):D. Eigen (NIPS2014)
  9. 题解:100元买100只鸡,公鸡4元一只,母鸡3元一只,小鸡1元3只,问公鸡,母鸡,小鸡各买了多少只?
  10. Unity 5.3 官方VR教程(—)VR综述
  11. 卡内基梅隆大学计算机科学博士,美国卡内基梅隆大学博士需要几年
  12. Docker核心技术(一):镜像与容器
  13. JavaScript生成图形验证码
  14. java获取double类型区间随机数
  15. 网络安全体系与网络安全模型
  16. 树莓派部署BT下载机
  17. 一天一夜chromebook折腾心得
  18. 关于mybatis的分页实现
  19. Android 6.0权限请求相关及权限分组
  20. Learn to Augment: Joint Data Augmentation and Network Optimizationfor Text Recognition

热门文章

  1. 启动elasticsearch.bat时闪退
  2. 【数据库SQL实战】获取员工其当前的薪水比其manager当前薪水还高的相关信息
  3. 2021-06-21指针与变量 和字符数组作业。
  4. 第一次使用虚拟机(VMware)
  5. 函数 function
  6. uc/os--OSRdyTbl
  7. SQL反模式:实体-属性-值(EAV)问题(二)
  8. 赛尔号找不到服务器ip,赛尔号互通版
  9. 江城子·己亥年戊辰月丁丑日话凄凉
  10. HTML中来访时间,html记录用户的访问次数代码