众所周知,前端无法像原生APP一样直接操作本地文件 —— 否则打开一个网页操控JS就能把用户电脑上的文件偷光。所以需要通过用户触发:通常,用户可选择以下两种方式触发
- 通过
input type="file"
选择本地文件 - 通过拖拽方式把文件“拉到”指定地方
第一种是最常用的手段,通常还会自定义一个按钮,然后盖在他上面:因为type="file"
的input不容易改变样式。
如下笔者写一个选择控件,并将其包裹在form里:
<form>
<input type="file" id="file-input" data-t="file" name="fileContent" />
</form>
然后就可以用FormData获取整个表单的内容了
//jQuery
$("#file-input").on("change",function(){
console.log(`file name is ${this.value}`);
let formData=new FormData(this.form);
formData.append("fileName",this.value);
console.log(formData);
})
input中增加data-t属性是为了防止有些浏览器默认会将不认识的type值变为text
其中,获取的this.value
是本地文件路径,也就是说在浏览器无法获取到文件的真实存放位置。同时FormData打印出来是一个空的Object —— 但不是说它的内容是空的,只是它对前端开发者来说是“透明”(不可见)的,只能append添加字段。
FormData无法得到文件的内容,而使用FileReader可以读取整个文件的相关信息:用户选择文件后,通过input.files
就可以得到选中的文件。我们以图片为例说明:
$("#file-input").on("change",function(){
let fileReader=new FileReader(),
fileType=this.files[0].type;
fileReader.onload=function(){
if(/^image\/[jpg|png|gif]/.test(fileType)){
$(`<img src="${this.result}" />`).appendTo("body")
}
}
console.log(this.files[0]);
//base64方式读取:图片等文件通用读取方式
fileReader.readAsDataURL(this.files[0]);
})
运行之后,你会如愿发现在按钮下面多出来了一张图片!
但是,上面这段代码的运行顺序是什么呢?是和往常一样“顺序”执行吗?
你如果在onload里面加上console的话就会发现onload的代码是最后执行的!
onload加载,如果遇到图片、表格等,则会等到这些都加载完成后才执行里面的代码。
(这也是前端性能优化中建议在一些场景下用window.addEventListener('DOMContentLoaded',function(){})
代替window.onload
的原因)
把原始的File对象(代码中this.files[0]
)打印出来是这样的:
它是一个window.File的实例,包含了文件修改时间、文件名、文件大小、文件的mime类型等。如果需要限制上传文件大小就可以通过判断size属性是否超出范围,单位是字节,而要判断是否为图片文件就可以通过type类型是否以image开头 —— 通过判断文件名的后缀可能会有不准。还有!目前只有jpg/gif/png三种格式的图片可以用img展示出来。
代码中笔者实例化了一个FileReader,调用它的readAsDataURL并把File对象传给他,监听它的onload事件,load完读取的结果就在他的result属性里了。它是一个base64格式的,所以可以直接赋值给img的src。
其实FileReader除了可以读取为base64外,还可以读取以下格式:
以原始二进制方式读取,读取结果可直接转成整数数组:fileReader.readAsArrayBuffer(this.files[0]);
“巧”的是,ArrayBuffer内容也同样不可见。但是可以通过ArrayBuffer.length得到长度,还能转成整形数组,从而知道文件的原始二进制内容:
let buffer=this.result;
//依次每字节8位读取,放到一个整数数组
let view=new Uint8Array(buffer);
console.log(view)
当然,我们再来说说这第二种方式:拖拽。
<div class="img-container">
drop your image here
</div>
也就一个框,里面一行字,让我们将css忽略。
去监听它的拖拽事件:
$(".img-container").on("dragover",function(event){
event.preventDefault();
})
.on("drop",function(event){
event.preventDefault();
let fileReader=new FileReader(),
file=event.originalEvent.dataTransfer.files[0];
let fileType=file.type;
fileReader.onload=function(){
if(/^image\/[jpg|png|gif]/.test(fileType)){
$(`<img src="${this.result}" />`).appendTo('.img-container');
}
}
fileReader.readAsDataURL(file);
});
代码中运用了es6的链式调用,不过这不重要。由上面可以知道:这种方式数据在event的dataTransfer对象里。拿到这个对象以后,就可以和第一种方式:输入框一样了,即使用FileReader读取。
或者新建一个空的formData,然后把它append到formData里:
let formData=new FormData();
formData.append("fileContent",file);