图片上传知识点梳理

46次阅读

共计 3900 个字符,预计需要花费 10 分钟才能阅读完成。

在日常项目开发中,图片上传是一个十分常见的场景。而现在的各种 UI 框架都提供了自己的上传组件,网上第三方的上传组件也多如牛毛。可能你早已习惯了直接使用这些现成的组件,然而对于其具体的实现,却并未深入解析。本文将通过简单的代码,为你解析图片上传的各个知识点。
样式自定义
既然是上传,肯定需要使用到 <input type=”file”> 标签了。然而,默认的 input 到标签样式不仅单一,且在各个浏览器下的表现也不相同,所以通常需要对 input 进行样式自定义。但 <input> 标签对于样式的修改并不十分友好。解决方法很多,最常用的是将 <input type=”file”> 标签隐藏,然后通过一个 <label> 标签进行关联,然后直接修改 <label> 标签的样式来实现。代码如下:
<input type=”file” id=”uploadImg”>
<label for=”uploadImg”> 点击上传 </label>
图片校验
在上传之前,一般会对文件进行各种校验,例如文件类型,大小,格式,尺寸等。
其中文件类型,可通过设置 <input> 标签的 accept 来指定文件类型。但 accept 属性并不能完全禁止用户上传指定类型之外的文件。故可以通过上传文件的各个属性进行校验拦截。校验前,我们需要通过 change 事件的事件对象来获取到上传的文件:
event.target.files
可以获取到上传文件列表。列表中对象包含如下信息:
{
lastModified: 1524153515000
lastModifiedDate: Thu Apr 19 2018 23:58:35 GMT+0800 (中国标准时间) {}
name: “589adfbfe821c.jpg”
size: 1357444
type: “image/jpeg”
webkitRelativePath: “”
}
从该对象中,我们可以获取到文件大小,文件类型,文件名等信息,从而可以在上传之前对这些信息进行校验,从而拦截掉不合法的文件。
然而,从 file 对象中,我们并不能获取图片的尺寸信息。而在我们的业务中,很多场景都需要限制上传图片的尺寸为某一个固定值,或者是某一个比例。以减少后期显示时的适配问题。要实现对上传图片尺寸对校验,我们需要使用到 FileReader 和 Image。
FileReader 对象允许 Web 应用程序异步读取存储在用户计算机上的文件。
Image()函数将会创建一个新的 HTMLImageElement 实例。它的功能等价于 document.createElement(‘img’)。
这里,我们需要用到 fileReader 的 readAsDataURL()方法来读取上传文件信息,通过 onload 处理事件来获取读取到的文件信息。如下:
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = e => {
console.log(e.target.result)
}
代码中,file 为我们之前获取到的文件列表 files 中的文件对象。e.target.result 为读取到到文件内容。
之后通过 new Image()函数创建一个新的 HTMLImageElement 实例,并将该实例的 src 赋值为 fileReader 读取到到文件内容。即可得到一个该文件的 HTMLImageElement 实例,通过该实例,我们便可以读取到该图片的尺寸信息。具体代码如下:
const image = new Image();
image.src = e.target.result;
image.onload = () => {
console.log(image.width, image.height);
}
图片上传预览
在之前的开发中,图片上传显示通常会采用先将文件上传,预览图片直接展示上传到服务器中到图片来实现,但这样无法达到上传前预览该图片的目的,且会造成许多垃圾图片的上传。
通过前面对于获取图片尺寸研究。相信你能很快想到一种更加优雅的图片预览方案,既然我们已经获取到了该文件的 HTMLImageElement 实例,那么我们直接将该实例 append 到页面的容器 Dom 中不久行了。或者直接将获取到的文件设置到已存在的 image 标签的 src 属性中。图片上传预览就是这么简单。
图片上传与上传进度展示
图片的上传,我们可以直接通过 form 标签搭配表单的 submit()方法来实现图片的上传。然而,这样我们就无法在上传前进行上传文件的校验与拦截。同时需要用户主动触发提交操作。要想让我们之前做的上传前的拦截工作不白做,我们需要去在合适的时候,主动触发文件的上传操作。
这里,需要使用到 FormData 对象,来将入参对象数据转为表单数据。
FormData 对象用以将数据编译成键值对,以便用 XMLHttpRequest 来发送数据。其主要用于发送表单数据,但亦可用于发送带键数据 (keyed data),而独立于表单使用。如果表单 enctype 属性设为 multipart/form-data,则会使用表单的 submit() 方法来发送数据,从而,发送数据具有同样形式。
首先我们创建一个 formData 对象,然后通过 append() 方法来添加字段。如下:
const formData = new FormData();
formData.append(“file”, file);
注意,formData 虽然为一个对象,但通过 console.log 却无法打印出其具体的值,只会得到 FormData {}。
接下来创建一个 XMLHttpRequest 对象, 用来发送 ajax 请求。并且通过该 XMLHttpRequest 对象的 upload.onprogress 方法,可以实时获取到上传信息,并进一步获取到上传的进度。具体代码如下:
const client = new XMLHttpRequest()
client.open(“POST”, uploadUrl)
client.upload.onprogress = function(e) {
if (e.lengthComputable) {
let total = e.total;
let loaded = e.loaded;
let percentage = parseFloat(loaded / total).toFixed(2);
}
}
client.send(formData)
上面代码中,uploadUrl 为上传的 URL。通过 upload.onprogress 的事件对象,可以获取到当前进度已上传的文件大小以及完整文件大小,通过这两个大小参数,可以很容易计算出已上传文件的比例,之后是显示上传进度条、还是展示进度数据,就可以随意操作了。
拖拽上传
除了传统的点击选择上传文件外,拖拽文件上传也是一个十分常见的场景。要使用拖拽上传,就需要使用 H5 的拖放方法 drop 和 drag 方法。除了这两个主要的方法外,还有拖放的不同阶段触发的多个方法,常用的拖拽方法如下:

ondragstart 事件:当拖拽元素开始被拖拽的时候触发的事件(作用对象为被拖曳元素)
ondrag:在元素拖动期间不停的触发该事件,与 touchmove 事件类似。(作用对象为被拖曳元素)
ondragend 事件:当拖拽完成后触发的事件(作用对象为被拖曳元素)
ondragenter 事件:当拖曳元素进入目标元素的时候触发的事件(作用对象为目标元素)
ondragover 事件:拖拽元素在目标元素上移动的时候触发的事件(作用对象为目标元素)
ondragleave 事件:拖拽元素在目标元素上移动的时候触发的事件(作用对象为目标元素)
ondrop 事件:被拖拽的元素在目标元素上同时鼠标放开触发的事件(作用对象为目标元素)

拖拽的各个事件类似与 touch 事件的各个阶段。然而需要注意的是,拖拽的各个事件,有着自己的作用对象,作用对象分为‘被拖拽元素’和‘目标元素’。被拖拽元素 为拖拽的那个 Dom 元素,主要使用在页面内 Dom 拖拽移动的场景。目标元素为接收被拖拽元素的元素区域。当被拖拽元素进入到该区域,便会触发目标对象的一系列事件。
在图片拖拽上传这个业务场景中,被拖拽元素为页面外部的图片文件,故此处仅用到目标元素的各个事件。我们可以通过这些事件来修改目标区域样式等。核心的两个事件为 ondragover 和 ondrop 事件。可能你觉得我只需要在松开鼠标时获取拖拽的文件就行,因此只需要使用 ondrop 事件就行了?但是,由于浏览器的默认行为,ondrop 事件并不会被触发。因此,需要使用 e.preventDefault(); 来阻止掉 ondropover 的浏览器默认事件,从而保证 ondrop 事件的触发。通过 ondrop 事件的事件对象,我们可以获取到跟 event.target.files 相同的文件列表,获取方法为 event.dataTransfer.files; 然而,当你这么写完之后,进行拖拽之后,你会发现浏览器自动跳转到了该图片的预览页。这也是由于浏览器的默认行为导致,因此也需要使用 e.preventDefault(); 来阻止掉浏览器的默认行为。这样,便可以进行后续的文件校验操作来。具体实现代码如下:
<label for=”uploadImg”
onDragOver={e => {
e.preventDefault();
}}
onDrop={e => {
if (e.dataTransfer) {
e.preventDefault();
const file = e.dataTransfer.files[0];

}
}}
</label>
至此,图片上传的常用知识点以梳理完毕,欢迎补充。

正文完
 0