Web文件上传总结
创始人
2025-05-28 07:58:35

文章目录

    • 指定文件类型
    • 多文件选择
    • 自定义样式
      • 通过 click() 方法使用隐藏的 file input 元素
      • 使用 label 元素来触发一个隐藏的 file input 元素
    • 基本上传方式
    • 访问文件
      • 传统的 DOM 选择器访问一个已经被选择的文件
      • 通过 change 事件访问被选择的文件
      • 动态添加change监听器
    • Ajax 上传
    • 监测上传进度
    • 分割上传
    • 拖拽上传

文件上传是 Web 开发常见需求,上传文件需要用到文件输入框。

指定文件类型

  • 一个以英文句号(“.”)开头的合法的不区分大小写的文件名扩展名。例如:.jpg.pdf.doc

  • 一个不带扩展名的 MIME 类型字符串。

  • 字符串 audio/*,表示“任何音频文件”。

  • 字符串 video/*,表示“任何视频文件”。

  • 字符串 image/*,表示“任何图片文件”。

accept 属性的值是包含一个或多个(用逗号分隔)唯一文件类型说明符的字符串。例如,一个文件选择器需要能被表示成一张图片的内容,包括标准的图片格式和 PDF 文件,大概是这样的:


可以用 accept 属性指定可接受的文件类型,它是一个以逗号间隔的文件扩展名和 MIME 类型列表。一些例子如下所示:

  • accept=“image/png” 或 accept=“.png”——接受 PNG 文件。
  • accept=“image/png, image/jpeg” 或 accept=“.png, .jpg, .jpeg”——接受 PNG 或 JPEG 文件。
  • accept=“image/*”——接受任何带有 image/* MIME 类型的文件。(许多移动设备也允许用户在使用它时用摄像头拍照。)
  • accept=“.doc,.docx,.xml,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document”——接受类似于 MS Word 文档的任何文件。

capture 属性是一个字符串,如果 accept 属性指出了 input 是图片或者视频类型,则它指定了使用哪个摄像头去获取这些数据。值 user 表示应该使用前置摄像头和(或)麦克风。值 environment 表示应该使用后置摄像头和(或)麦克风。如果缺少此属性,则用户代理可以自由决定做什么。如果请求的前置模式不可用,则用户代理可能退回到其首选的默认模式。

不支持的浏览器会自动忽略这些属性

在实际开发中,安卓与苹果表现不一致,更多使用APP封装的调用方法,类似微信js-sdk方案。

 //只调用相机
 //只调用摄像机
 //只调用录音设备,苹果表现依然为调用摄像机 //调用相机与文件相册
 //调用摄像机与文件视频
 //调用录音设备与文件录音

多文件选择

如果给文件输入框添加一个 multiple 属性则可以一次选择多个文件


自定义样式

通过 click() 方法使用隐藏的 file input 元素

  1. 你可以通过给 input 元素添加 display:none 的样式,
  2. 再调用 元素的 click() 方法来实现,
  3. 同时监听 file 元素的change事件,获取上传的文件信息。

选择文件

使用 label 元素来触发一个隐藏的 file input 元素

  1. 允许在不使用 JavaScript(click() 方法)来打开文件选择器,可以使用 元素。
  2. 注意在这种情况下,input 元素最好不使用 display: none(或 visibility: hidden)隐藏,否则 label 将无法通过键盘访问。



这里不需要添加任何 JavaScript 代码来调用fileElem.click(),另外,这时你也可以给 label 元素添加你想要的样式。您需要在其 label 上提供隐藏 input 字段的焦点状态的视觉提示,比如上面用的轮廓,或者背景颜色或边框阴影。

基本上传方式

当把文件输入框放入表单中,提交表单的时候即可将选中的文件一起提交上传到服务器,需要注意的是由于提交的表单中包含文件,因此要修改一下表单元素的 enctype 属性为 multipart/form-data

这是传统的上传方式,且无法自定义请求header中的参数(如token信息),目前基本不会采用这种方式进行,仅做了解。

访问文件

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 事件访问被选择的文件

function handleFiles (files) {console.log(files)
}

动态添加change监听器

你需要使用 EventTarget.addEventListener() 去添加 change 事件监听器,像这样:

const inputElement = document.getElementById("input");
inputElement.addEventListener("change", handleFiles, false);
function handleFiles() {const fileList = this.files;
}

注意在这个例子里,handleFiles() 方法本身是一个事件处理器,不像之前的例子中,它被事件处理器调用然后传递给它一个参数。

Ajax 上传

由于可以通过 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 确定,BlobFile 的父类)

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()函数。在这之后,处理文件的方法与之前一致。

相关内容

热门资讯

浮萍居主的作品 ,浮萍居主的作... 浮萍居主的作品 目录浮萍居主的作品 浮萍居主的作品朔月房心哪里能看浮萍居主的作品 浮萍居主是一个网络...
林正英鬼片有哪些,l林正英的鬼... 林正英鬼片有哪些目录林正英鬼片有哪些l林正英的鬼片和僵尸片里一共有多少部?什么鬼片好看的最好是林正英...
电视剧小欢喜剧情介绍 ,小欢喜... 电视剧小欢喜剧情介绍 目录电视剧小欢喜剧情介绍 小欢喜影视剧改编电视剧《小别离》和《小欢喜》差不多嘛...
想你大结局是什么 ,想你的男主... 想你大结局是什么 目录韩剧想你的结局是什么T_T想你的男主角最后死吗?韩剧《想你》大结局是怎么样的呀...
抽丝剥茧还原真相,记一次神奇的... 作者:靳倡荣 本文详细回放了一个崩溃案例的分析过程。回顾了C++多...
力扣-超过经理收入的员工 大家好,我是空空star,本篇带大家了解一道简单的力扣sql练习题。 ...
什么是叫做中土世界 ,科幻电影... 什么是叫做中土世界 目录什么是叫做中土世界 科幻电影中所说的中土世界是什么地方中土世界的涵义《指环王...
余罪小说结局是什么,余罪小说结... 余罪小说结局是什么目录余罪小说结局是什么余罪小说结局余罪小说结局牲口余罪的结局是什么?余罪小说结局是...
力学性能主要包括哪些指标 ,力... 力学性能主要包括哪些指标 目录力学性能主要包括哪些指标 力学性能主要包括哪些指标什么叫金属的力学性能...
数据记录仪自动测量与记录加速度... 使用数据记录仪测量加速度 什么是加速度数据记录仪?我们如何记录振动?颠簸...
芯片设计中的LEF文件浅析 LEF和DEF是APR工程师工作中经常会碰到的两类文件,也会对APR的基础配置和APR...
没有关系的话,那就去建立关系吧         今天给大家分享一道链表的好题--链表的深度拷贝,学会这道题࿰...
javaSE系列之抽象类与接口 💗 💗 博客:小怡同学 💗 💗 个人...
倾巢之下安有完卵意思 ,“覆巢... 倾巢之下安有完卵意思 目录倾巢之下安有完卵意思 “覆巢之下安有完卵”是什么意思“覆巢之下安有完卵”是...
九州海上牧云记结局 ,海上牧云... 九州海上牧云记结局 目录九州海上牧云记结局 海上牧云记结局是什么?灿烂的遗产。结局是什么?《九州海上...
细胞核用什么染色 ,细胞核用什... 细胞核用什么染色 目录细胞核用什么染色 细胞核用什么试剂染色因为有很多方法,请列举试剂名称细胞核用什...
齐B小短裙是什么意思,齐B小短... 齐B小短裙是什么意思目录齐B小短裙是什么意思齐B小短裙是什么意思 齐B小短裙是什么梗齐B小短裙是什么...
OpenFunction v1... OpenFunction 是一个开源的云原生 FaaS(Function as a S...
Linux lvm管理讲解及命... ♥️作者:小刘在C站 ♥️个人主页:小刘主页 ♥️每天分享云计算网络运维...
重压下彰显经营韧性,屈臣氏持续... 2022年化妆品行业承受着较大压力。国家统计局的数据显示,2022年化妆品零售总额跌破...
走进哈希心房   目录 哈希的概念 哈希函数 哈希冲突和解决方法 闭散列 插入 查找 删除 开散列 插入 查找 删...
电影七宗罪讲的是什么 ,求电影... 电影七宗罪讲的是什么 目录电影七宗罪讲的是什么 求电影《七宗罪》剧情简介以及故事结果。七宗罪这部电影...
剪刀手爱德华扮演者是谁 ,剪刀... 剪刀手爱德华扮演者是谁 目录剪刀手爱德华扮演者是谁 剪刀手爱德华饰演爱德华的是谁剪刀手爱德华的演员剪...
多少次痛到不得已是什么歌 ,《... 多少次痛到不得已是什么歌 目录多少次痛到不得已是什么歌 《逃爱》的歌词是什么?歌词,多少痛只好留心中...
千人斩是什么意思 ,1000人... 千人斩是什么意思 目录千人斩是什么意思 1000人斩什么意思av里什么叫千人斩千人斩是什么意思?千人...
LC-146.LRU 缓存 题解:https://leetcode.cn/problems/lru-cache/s...
太平公主秘史剧情介绍 ,太平公... 太平公主秘史剧情介绍 目录太平公主秘史剧情介绍 太平公主秘史32集剧情太平公主秘史剧情介绍太平公主秘...
步步惊心歌曲有哪些 ,步步惊心... 步步惊心歌曲有哪些 目录步步惊心歌曲有哪些 步步惊心里的全部歌曲名步步惊心上的所有歌曲名字是什么?步...
一千零一夜结局,一千零一夜大结... 一千零一夜结局目录一千零一夜结局一千零一夜大结局一千零一夜大结局是什么一千零一夜结局一千零一夜的结局...
命运石之门结局是什么 ,命运石... 命运石之门结局是什么 目录命运石之门结局是什么 命运石之门结局《命运石之门》的结局是什么意思?命运石...