0%

上传图片前的预览操作

上传图片前的预览

准备工作

这次我们脱离任何框架,直接用JavaScript原生的来写。准备一个html:

1
2
3
4
5
6
7
8
9
10
<div class="input-area">
<input
id="input-el"
type="file"
accept="image/*"
multiple
onchange="onChange(event)"
/>
</div>
<div id="preview-area"></div>

如图所示,首先我们有了一个input,用来选择图片;然后有个id="preview-area"的区域用来展示预览。

input的触发选择文件事件后,会调用onChange方法来处理文件。

然后准备js:

1
2
3
4
5
6
7
8
// 处理好的预览的图片对象
const previewArr = []
let previewEl, inputEl;

// 处理选择图片后的操作
function onChange(e) {}
// 当图片处理后的渲染
function render() {}

处理选中的图片

当我们点击页面上的选择图片输入框后,就会弹起选择图片,然后点击选中图片后,会触发onChange()方法。在onChange中处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function onChange(e) {
if (!inputEl) {
inputEl = document.getElementById('input-el')
}
const target = e.target;
if (!target.files || !target.files.length) {
return
}
for(const file of target.files) {
loaderImagePreview(file)
}
inputEl.value = null
render();
}

这个方法做三步:

  • 判断是否有选择图片
  • 循环处理图片,处理图片的方法为loaderImagePreview
  • 图片处理完后调用render()进行渲染

所以重点代码就在loaderImagePreview()函数中。

1
2
3
4
5
6
7
8
9
10
11
12
13
function loadImagePreview(imageFile) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.readAsDataURL(imageFile)
reader.onload = (event) => {
resolve({
file: imageFile,
preview: event.target.result.toString(),
id: new Date().getTime() + Math.floor(Math.random() * 10).toString()
})
}
})
}

这里使用了FileReader。关于FileReaderMDN Web API FileReader上有介绍:

FileReader对象运行web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区内容),使用FileBlob对象指定要读取的文件或数据。其中File对象可以是来自用户在一个<input>元素上选择文件后返回的FileList对象,也可以来自拖放操作生成的DataTransfer对象,还可以是来自在一个HTMLCanvasElement上执行mozGetAsFile()方法后返回的结果。

我们在构造了FileReader的实例后,调用了FileReader.readAsDataUrl()方法。这个方法用来读取指定的Blob中的内容,一旦完成,result属性中将包含一个data:URL格式的Base64字符串以表示读取的文件内容。

所以我们还需要使用FileReader.onload事件来监听这个读取事件是否完成,在完成的时候执行我们的逻辑。

因为文件的读取是异步的,所以我们使用了promise来包装了这个函数,使得我们可以在别的地方用同步的方法调用。

onload事件触发的时候说明文件读取完成了,我们resolve了一个对象,包含原始的File文件、读取后的base64字符串、以及一个特定id。这个id后面会用到。

那么我们需要修改下onChange()函数中loaderImagePreview()方法的调用方式了:

1
2
3
4
5
6
7
8
async function onChange(e) {
// ...
for (const file of target.files) {
const previewItem = await loadImagePreview(file)
previewArr.push(previewItem)
}
// ...
}

是不是好奇onChange()方法最后的inputEl.value = null是干嘛的?这是为了在处理图片后,重置input,要不然我们触发的onChange里面的target.files会不可控。

这样,处理的图片的逻辑就完了。

渲染

现在处理好了,我们需要渲染到页面上,毕竟我们没用框架,所以还得我们手动去执行渲染。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function render() {
if (!previewEl) {
previewEl = document.getElementById('preview-area')
}
const previewIdDic = {}
if (previewEl.hasChildNodes()) {
previewEl.childNodes.forEach(item => {
console.log('id', item.attributes['id'])
previewIdDic[item.attributes['id']['value']] = true
})
}
previewArr.forEach(item => {
console.log(item.id)
if (previewIdDic[item.id]) {
return
}
const img = document.createElement('img')
img.src = item.preview
img.alt = 'preview'
img.id = item.id
previewEl.appendChild(img)
})
}

我们首先需要判断下渲染区域里面是不是已经有预览的图片了,如果有的话,我们需要将图片id读取出来放在一个字典对象里面。

然后开始遍历预览图片的数组,通过比对id,可以知道哪些是已经渲染了的,就不会重复渲染。

最后完整的代码:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>上传图片前的预览</title>
<script>
const previewArr = []
let previewEl, inputEl;

async function onChange(e) {
if (!inputEl) {
inputEl = document.getElementById('input-el')
}
const target = e.target;
if (!target.files || !target.files.length) {
return
}
for (const file of target.files) {
const previewItem = await loadImagePreview(file)
previewArr.push(previewItem)
}
inputEl.value = null
render();
}

async function loadImagePreview(imageFile) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.readAsDataURL(imageFile)
reader.onload = (event) => {
resolve({
file: imageFile,
preview: event.target.result.toString(),
id: new Date().getTime() + Math.floor(Math.random() * 10).toString()
})
}
})
}

function render() {
if (!previewEl) {
previewEl = document.getElementById('preview-area')
}
const previewIdDic = {}
if (previewEl.hasChildNodes()) {
previewEl.childNodes.forEach(item => {
console.log('id', item.attributes['id'])
previewIdDic[item.attributes['id']['value']] = true
})
}
previewArr.forEach(item => {
console.log(item.id)
if (previewIdDic[item.id]) {
return
}
const img = document.createElement('img')
img.src = item.preview
img.alt = 'preview'
img.id = item.id
previewEl.appendChild(img)
})

}

</script>
</head>

<body>
<div class="input-area">
<input id="input-el" type="file" accept="image/*" multiple onchange="onChange(event)"/>
</div>
<div id="preview-area"></div>
</body>
</html>

脱离了框架写东西也蛮有趣的。🙂

码字辛苦,打赏个咖啡☕️可好?💘