在浏览器中用 JavaScript 上传文件 (1/3) 前端的代码
一些记录与例子。
- Part 1/3:前端的代码【这一篇】
- Part 2/3:multipart/form-data 的 POST 请求
- Part 3/3:后端的代码(Golang 版)
1 客户端的效果
▶︎ **例子 1-1:**简单的演示。其中,“提交”按钮不能真正提交文件,而只作演示用(因为没有服务器)。
◀︎
以下是基本知识点。
2 文件选择元素
▶︎ **例子 2-1(<input type='file'>
):**在上面的例子 1-1 中,用户点击“添加更多文件”,就能调出文件选择窗口;这是用 input
实现的。
input 是一种 HTML 元素。当 input 的 type
属性被设置为 file
,即:
<!-- 例子 2-1 代码:文件选择元素 -->
<input type='file'>
就可以选择文件了。效果如下:
◀︎
为了后续讨论方便,定义:
- type 属性为 file 的 input 元素被称为 文件选择元素。
- 点击文件选择元素自带的按钮之后,跳出来的窗口被称为 文件选择窗口。
注意:input 是不需要关闭标签的;换言之,
<!-- input 元素的不规范写法 -->
<input type='file'></input>
这样的写法是不规范的。
2.1 multiple
属性:一次选择多个文件
文件选择元素的一个重要属性是 multiple
,它决定了之后跳出的文件选择窗口是否可以选择多个文件。
带 multiple 的文件选择元素可以选择多个文件。下面是例子:
▶︎ 例子 2-2:
<!-- 例子 2-2 代码:可以选择多个文件的文件选择元素 -->
<input type='file' multiple>
效果:
上面这个文件选择元素允许使用者选择多个文件,来试一试吧。 ◀︎
2.2 click
方法:用 JavaScript 调出文件选择窗口
多数时候,出于美观考虑,我们希望用自定义的按钮调出文件选择窗口,也就是自己搞一个按钮,然后一点这个按钮,就可以让我们选择文件。
为了实现这一需求,可以先通过增加 display:none;
样式,把文件选择元素隐藏起来;
然后需要调出文件选择窗口的时候,通过 JavaScript 调用文件选择元素的 click
方法。
以下是例子:
▶︎ 例子 2-3:
<!-- 例子 2-3 代码:用 JavaScript 调出文件选择窗口 -->
<!-- 自定义样式的按钮: -->
<div
id='elemButton'
style='cursor:pointer;max-width:15em;border:1px solid black;border-radius:5px;background:#3af;color:#fff;text-align:center;margin:0 auto;padding:5px;'
>
点我选择文件
</div>
<!-- 被隐藏的文件选择元素: -->
<input
id='elemFileSelector'
type='file'
style='display:none;'
multiple
>
<script>
document.getElementById('elemButton').addEventListener('click', function() {
// 调用 click 方法,调出文件选择窗口
document.getElementById('elemFileSelector').click();
});
</script>
效果如下:
只要点击上面的按钮,就可以调出文件选择窗口了。 ◀︎
2.3 change
事件
当用户通过文件选择元素选中了新的文件时,change 事件会被触发。
有时候,我们希望用户选中文件以后,系统能弹出一个对话框,上面有“您已经选择了文件!”的提示。为了实现这一需求,我们可以把弹出对话框的代码写到一个匿名函数里,再把这个匿名函数通过 addEventListener 绑定为 change 事件的回调函数。
▶︎ 例子 2-4:change 事件演示
<input type='file' id='elemFileSelector'>
<script>
document.getElementById('elemFileSelector').addEventListener('change', function() {
alert('您已经选择了文件!');
});
</script>
效果如下:
◀︎
2.4 files
属性、FileList
对象、File
对象
选中文件之后,可以用 JavaScript 通过文件选择元素的 files
属性来访问被选中的文件的信息。
以下是一个试验:
试验:通过 files 属性获取选中文件的信息
准备:需要有一个能打开开发者面板的 PC 端浏览器。
- 选文件。上面是一个
id
属性为elemFileSelector2
文件选择元素。首先点击按钮,然后选择 3 个文件。- 然后,打开开发者面板的控制台。
- 接着,执行下面这一行代码。这行代码的作用是,将
elemFileSelector2
的files
属性值打印到控制台上。
console.log(document.getElementById('elemFileSelector2').files);
- 最后,观察输出结果。
输出截图如下(会根据浏览器的不同而有样式上的不同):
截图中的输出结果是一个有着 3 个 File
对象的 FileList
对象。
也就是说,文件选择元素的 files
属性的值,是一个 FileList。
FileList 的访问方法与数组类似,都是通过方括号算符来访问的。 比如说,如果想把首个File对象输出到控制台上,可以这样:
var selectedFiles = document.getElementById('elemFileSelector').files; // 其中,elemFileSelector 为文件选择元素的 id。
// selectedFiles 的类型是 FileList,
// selectedFiles[0] 的类型是 File。
console.log(selectedFiles[0]);
FileList 不是数组,没有 push 和 pop 之类的方法。不过,FileList 也有 length 属性。 也就是说:
console.log(selectedFiles.length);
这行代码可以输出选中了多少文件。
FileList 里面是一堆 File 对象。每个 File 对象中,有这四个属性:
lastModified
:文件最后一次被修改的时间。是数字。以毫秒为单位的 UNIX 时间戳。name
:文件名。size
:文件的大小。数字。以字节为单位。type
:文件的 MIME 类型。其值由且只由文件后缀决定。比如说,有一个 png 图象文件,但是这个图象文件的后缀是.txt
,那么其对应 File 对象的 type 值就是text/plain
,而不是image/png
。
这四个属性都是只读的。
2.5 读取 File 对象的文件内容(略)
上述的四个属性都是用来描述文件的元信息的。文件里面的内容无法光靠这四个属性读取。想要读取 File 对象对应的文件内容,可以这样:
// 假设 fileObject 是一个 File 对象:
fileObject.arrayBuffer().then(function(buf) {
var byteArray = new Uint8Array(buf);
});
上面的代码构建了一个名为 byteArray、类型为 Uint8Array 的变量。
Uint8Array 为 fileObject 所指的 ArrayBuffer 提供数据视图,允许程序员对那一块 ArrayBuffer 进行读写。(待查)
想要查看 byteArray 里面的内容,可以用 console.log 输出:
fileObject.arrayBuffer().then(function(buf) {
var byteArray = new Uint8Array(buf);
console.log(byteArray);
});
3 异步上传文件:XMLHttpRequest
与 FormData
假设现在有一个名为 fileObject 的变量,指向一个 File 对象。通过以下代码,可以把这个对象代表的文件通过 POST 方法上传到 http://target-url.com/
。
// 方法 1:
var formData = new FormData();
formData.append('fileobject', fileObject); // 将 fileObject 包装进 formData 中。
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://target-url.com');
xhr.send(formData);
在大多数新版本的浏览器中,也可以直接把 fileObject 作为 xhr.send
的参数:
// 方法 2:
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://target-url.com/');
xhr.send(fileObject);
这两种方法的一些区别:
方法 1 - xhr.send(<FormData>) | 方法 2 - xhr.send(<File>) | |
---|---|---|
请求体 | multipart/form-data | 普通的 Post Request,文件的内容作为 Request Payload |
浏览器支持 | Chrome 6+(更广泛的支持) | Chrome 22+ |
具体的区别,将在下一篇(Part 2/3)记录。
旁注:
XMLHttpRequest
的progress
事件有时候,我们会想要得知文件上传的进度,比如做一个进度条之类的。这个需求可以通过监听 XMLHttpRequest 的 progress 事件来实现。具体方法见 MDN Doc - Progress Event。