上传图片前的预览
准备工作
这次我们脱离任何框架,直接用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
。关于FileReader
在MDN Web API FileReader上有介绍:
FileReader
对象运行web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区内容),使用File
或Blob
对象指定要读取的文件或数据。其中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>
|
脱离了框架写东西也蛮有趣的。🙂