JS34 文件上传前实时预览

上传图片前在本地预览的方法。

概述

上传图片前在本地预览

当使用<input type="file">上传图片文件时,如何在上传到服务器之前,在本地进行预览?

FileReader对象

FileReader对象允许Web应用程序异步读取用户计算机上的文件。使用new创建一个FileReader实例对象:

1
const fileReader = new FileReader();

属性

  • error:在读取时发生的错误
  • readyStatefileReader当前状态
  • result:读取到的文件内容,只有在读取操作完成后有效

方法

  • abort():终止读取操作
  • readAsDateURL():读取文件中的内容,读取完成后调用onloadend方法,结果result中包含一个data:URL格式的字符串表示文件内容(针对图片就是base64格式的字符串)

事件处理程序

  • onabort:读取被终止时被调用
  • onerror:读取错误时调用
  • onload:读取成功时调用
  • onloadend:读取完成时调用,无论成功失败, 在onloadonerror后调用
  • onloadstart:读取开始前调用
  • onprogress:读取过程中周期调用、

兼容性

可以看出除了IE系列支持不太好之外,包括Edge在内兼容性还是不错的。

使用FileReader对象预览图片

  1. <input>onchange事件中获取上传的图片对象
  2. 使用<input>event.target.files获取上传对象的类数组对象,每一项的name属性对应文件名
  3. <input>中增加multiple属性,上传多个文件
  4. 创建FileReader对象,并通过readAsDateURL()方法,传入要预览的文件对象,在onload回调函数中对FileReader对象的result进行处理

预览多张图片的Demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<body>
<label>
<input type="file" id="file" multiple>
</label>
<div id="preview" class="preview">
<img id="img" src="default.png">
</div>
</body>

<script>
'use strict';
const input = document.querySelector('#file'),
wrapper = document.querySelector('#preview'),
img = document.querySelector('#img');

function preview(files) {
Object.keys(files).forEach(function (file) {
const fileReader = new FileReader();
fileReader.readAsDataURL(files[file]);
fileReader.onload = function () {
img.src = fileReader.result;
img.title = files[file].name;
};
})
}

input.onchange = function (e) {
let files = e.target.files;
preview(files)
};
</script>

由于FileReader读取的过程是一个异步函数,我们可以封装为一个Promise形式进行更方便的调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function asyncPreview(file) {
return new Promise(((resolve, reject) => {
const fileReader = new FileReader();
fileReader.readAsDataURL(file);
fileReader.onload = t => {
console.log('preview success!');
resolve(t.target.result);
};
fileReader.onerror = e => {
console.log('preview fail!', e);
reject(e)
}
}))
}

拖拽预览

不通过点击事件而是通过将图片拖拽到指定区域实现预览。

在拖放过程中会触发的事件:

  • 在源元素上触发的事件(需要设置 draggable 属性)
    • ondragstart:开始拖动时触发
    • ondrag:拖动时触发
    • ondragend:拖动完成时触发
  • 释放时触发的事件
    • ondragenter:进入容器范围时触发
    • ondragover:拖动时触发(触发间隔350毫秒)
    • ondragleave:离开容器范围时触发
    • ondrop:拖动过程中,释放鼠标按键时触发

显然这里需要使用的是ondrop事件,但是需要注意,使用ondrop事件需要阻止浏览器dragenterdragleavedragoverdrop事件的默认行为,否则不会触发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 // 释放
document.addEventListener("drop", function(e) {
e.preventDefault();
e.stopPropagation();
});

// 拖进
document.addEventListener("dragenter", function(e) {
e.preventDefault();
e.stopPropagation();
});

// 拖离
document.addEventListener("dragleave", function(e) {
e.preventDefault();
e.stopPropagation();
});

// 拖来拖去
document.addEventListener("dragover", function(e) {
e.preventDefault();
e.stopPropagation();
});

ondrop事件中触发上面的函数preview就可以实现图片预览了。注意,inputonchange事件获取文件对象是e.target.files,而drop事件则是e.dataTransfer.files

如果报错event.js:501 Uncaught TypeError: this._drop is not a function是因为事件冒泡的原因,所以要增加e.stopPropagation()

window.URL.createObjectURL()

也可以通过这个方法来实现图片的预览

它对IE的支持比fileReader稍好一些

window.URL属性返回一个对象,它提供了用于创建和管理对象URLs的静态方法。它也可以作为一个构造函数被调用来构造URL对象。

URL.createObjectURL() 静态方法会创建一个 DOMString,这个新的URL 对象表示指定的File对象或Blob对象。

在每次调用createObjectURL()方法时,都会创建一个新的URL对象,即使已经用相同的对象作为参数创建过。当不再需要这些URL对象时,每个对象必须通过调用URL.revokeObjectURL()方法来释放。浏览器会在文档退出的时候自动释放它们,但是为了获得最佳性能和内存使用状况,你应该在安全的时机主动释放掉它们。

1
2
3
4
5
6
7
8
// 使用 createObjectURL
function preview(files) {
Object.keys(files).forEach(function(file) {
img.src = window.URL.createObjectURL(files[file]);
})
// 释放内存
window.URL.revokeObjectURL()
}

fileReaderURL.createObjectURL的区别

(1)兼容性

URL.createObjectURL()对IE10和IE11的支持更好一些,但是总体上来说,二者兼容性是差多不的

(2)异步

fileReader是异步读取的过程,而URL.createObjectURL()是同步读取的,所以如果是读取比较大的图片会对性能有一定影响。为了解决这个问题可以再Web Woker中完成读取的操作。

(3)释放

上面提到了,使用URL.createObjectURL()时,从性能角度出发,可以通过手动调用URL.revokeObjectURL()来释放URL对象

参考