前端文件上传下载,这里总有一种适合你!
简介 遇到前端文件上传下载,可能会先想到前人已经写好的轮子(模块或者第三方库),引入就能用了。如果想进一步了解,可以接着往下看,本文主要讲述前端文件上传下载,可能涉及到前端文件的数据类型,例如ArrayBuffer, TypedArray, DataView, Blob, File, Base64, FileReader
等。
下载 超链接下载 最常见的一种简单实现方式。
下载本地资源 :
给超链接加上 download 属性
1 <a href ="./img/logo.png" download ="testDownload" > 直接下载图片</a >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const a = document .createElement ('a' )a.href = '/xxxTemplate.xlsx' a.download = 'xxx模板.xlsx' a.style .display = 'none' document .body .appendChild (a)a.click () a.remove ()
下载服务器资源 :
这就需要用到Blob url
或者Base64 data
。
Blob url
,可以使用window.URL.createObjectURL(blob)
方法生成 Blob url
,然后将Blob url
赋值给超链接的href
属性,然后模拟点击超链接进行下载。
Blob url
,简单的理解一下就是将一个file
或Blob
类型的对象转为UTF-8
、UTF-16
等字符串,并保存在当前操作的document
下,存储在内存中。
生成blob url
使用的方法是URL.createObjectURL(file/blob)
。清除方式只有页面unload()
事件或者使用URL.revokeObjectURL(objectURL)
手动清除 。
Base64 data
,可以直接把Base64 data
赋值给超链接的href
属性,然后模拟点击超链接进行下载。
下面我将模拟后端返回ArrayBuffer
、Blob
对象和base64数据
来实现超链接方式下载。
后端返回 ArrayBuffer
:
因为生成Blob url
的参数只能是Blob
或File
对象,所以对于后端返回的ArrayBuffer
我们需要先转成Blob
或者File
对象然后再生成Blob url
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function aDownload1 ( ) { const str = 'hello huatree!' let ab = new ArrayBuffer (str.length ) let ia = new Uint8Array (ab) for (let i = 0 ; i < str.length ; i++) { ia[i] = str.charCodeAt (i) } const a = document .createElement ('a' ) a.download = 'test' const blob = new Blob ([ia], { type : 'text/plain' }) a.href = window .URL .createObjectURL (blob) a.style .display = 'none' document .body .appendChild (a) a.click () window .URL .revokeObjectURL (a.href ) document .body .removeChild (a) }
后端返回 Blob :
直接生成Blob url
就可以了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function aDownload2 ( ) { const blob = new Blob (['hello' , 'randy' ], { type : 'text/plain' }) const a = document .createElement ('a' ) a.download = 'test' a.href = window .URL .createObjectURL (blob) a.style .display = 'none' document .body .appendChild (a) a.click () window .URL .revokeObjectURL (a.href ) document .body .removeChild (a) }
后端返回base64
:
这种情况少见。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 async function aDownload3 ( ) { const b1 = await img2base64 ('./imgs/logo.png' ) const a = document .createElement ('a' ) a.download = 'test' a.href = b1 a.style .display = 'none' document .body .appendChild (a) a.click () document .body .removeChild (a) }
showSaveFilePicker API 下载 showSaveFilePicker 是一个新的api
,调用该方法后会显示允许用户选择保存路径的文件选择器。
1 const FileSystemFileHandle = Window .showSaveFilePicker (options)
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 async function download3 (blob, filename ) { try { const handle = await window .showSaveFilePicker ({ suggestedName : filename, types : [ { description : 'text file' , accept : { 'text/plain' : ['.txt' ] } }, { description : 'jpeg file' , accept : { 'image/jpeg' : ['.jpeg' ] } } ] }) const writable = await handle.createWritable () await writable.write (blob) await writable.close () return handle } catch (err) { console .error (err.name , err.message ) } } function showSaveFilePickerDownload ( ) { const blob = new Blob (['hello' , 'randy' ], { type : 'text/plain' }) download3 (blob, 'test.txt' ) }
当你点击下载后会出现文件选择界面,及你建议的文件名和文件类型选择。
相比 a 标签下载 的方式,showSaveFilePicker API
允许你选择文件的下载目录、选择文件的保存格式和更改存储的文件名称。不过可惜的是该 API
目前的兼容性还不是很好。详见
注意这种下载方式下载的文件不会出现在浏览器下载列表。
FileSaver 下载 FileSaver.js 是在客户端保存文件的解决方案,非常适合在客户端上生成文件的 Web 应用程序。
Browser
Constructs as
Filenames
Max Blob Size
Dependencies
Firefox 20+
Blob
Yes
800 MiB
None
Firefox < 20
data: URI
No
n/a
Blob.js
Chrome
Blob
Yes
2GB
None
Chrome for Android
Blob
Yes
RAM/5
None
Edge
Blob
Yes
?
None
IE 10+
Blob
Yes
600 MiB
None
Opera 15+
Blob
Yes
500 MiB
None
Opera < 15
data: URI
No
n/a
Blob.js
Safari 6.1+*
Blob
No
?
None
Safari < 6
data: URI
No
n/a
Blob.js
Safari 10.1+
Blob
Yes
n/a
None
对于FileSaver.js
我们主要需要记住他的这个saveAs
方法。
1 FileSaver .saveAs (Blob /File /Url , optional DOMString filename, optional Object { autoBom })
jszip 压缩下载 jszip 可以让下载的文件转为zip
格式。
jszip
自己不具备下载功能,他只是提供了将文件压缩成zip
包的功能,下载的话我们还是需要借助前面所说的FileSaver.js
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var zip = new JSZip ()zip.file ('Hello.txt' , 'Hello World\n' ) zip.file ('Hello2.txt' , blob) zip.file ('Hello3.txt' , file) zip.generateAsync ({ type : 'blob' }).then (function (content ) { FileSaver .saveAs (content, 'example.zip' ) })
附件形式下载 我们平时在浏览器输入图片链接地址,为什么有的图片是预览而有的却是直接下载呢?
这个链接在浏览器打开是直接预览
这个链接在浏览器打开是直接下载
这里就涉及到附件形式下载了。
我们可以通过设置 Content-Disposition 响应头来指示响应的内容以何种形式展示,是以内联(inline)的形式,还是以附件(attachment)的形式下载并保存到本地。filename 用来设置下载的文件的文件名。
1 2 3 Content -Disposition : inlineContent -Disposition : attachmentContent -Disposition : attachment; filename="filename.jpg"
打开控制台可以发现,我们上面的第二张图就是设置了Content-Disposition
响应头,所以我们输入完图片链接后会以附件的形式直接下载。
了解更多,详见
上传 不管使用何种方式,都是先获取到文件对象然后在利用表单FormData
对象进行传输。
单文件上传 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <input id ="uploadFile1" type ="file" accept ="image/*" /> <script > const upload = ( ) => { const uploadFileEle = document .querySelector ('#uploadFile1' ) const files = uploadFileEle.files let formData = new FormData () formData.append (fieldName, files[0 ]) } </script >
多文件上传 在input
元素里面添加multiple
属性,表示支持多文件上传。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <input id ="uploadFile2" type ="file" accept ="image/*" multiple /> <script > const upload = ( ) => { const uploadFileEle = document .querySelector ('#uploadFile2' ) const files = uploadFileEle.files let formData = new FormData () Object .values (files).forEach ((file, i ) => { formData.append ('file' + i, file) }) } </script >
文件夹上传 在input
元素里面添加webkitdirectory
属性,表示是文件夹上传。
该属性的兼容性如下,需要注意IE
是完全不支持的。详见
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <input id ="uploadFile3" type ="file" accept ="image/*" webkitdirectory /> <script > const upload = ( ) => { const uploadFileEle = document .querySelector ('#uploadFile3' ) const files = uploadFileEle.files let formData = new FormData () Object .values (files).forEach ((file, i ) => { formData.append ('file' + i, file) }) } </script >
以文件夹方式上传的话,在选择文件夹后会有个小提示。并且我们可以在File
对象里面通过webkitRelativePath
属性看到该文件的相对路径。
jszip 压缩上传 压缩上传就是将文件压缩成压缩包,然后再上传到服务端。压缩还是使用我们前面介绍的jszip 库。
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 <input id ="uploadFile4" type ="file" accept ="image/*" webkitdirectory /> <script > function generateZipFile (zipName, files, options = { type: 'blob' , compression: 'DEFLATE' } ) { return new Promise ((resolve, reject ) => { const zip = new JSZip () Object .values (files).forEach ((file, i ) => { zip.file ('file' + i, file) }) zip.generateAsync (options).then (function (blob ) { zipName = zipName || Date .now () + '.zip' const zipFile = new File ([blob], zipName, { type : 'application/zip' }) resolve (zipFile) }) }) } async function uploadFile ( ) { const uploadFileEle = document .querySelector ('#uploadFile4' ) const files = uploadFileEle.files let webkitRelativePath = fileList[0 ].webkitRelativePath let zipFileName = webkitRelativePath.split ('/' )[0 ] + '.zip' let zipFile = await generateZipFile (zipFileName, fileList) let formData = new FormData () formData.append ('zipfile' , zipFile) } </script >
拖拽上传 要实现拖拽上传的功能,我们需要先了解与拖拽相关的事件。比如 drag、dragend、dragenter、dragover 或 drop 事件等。
dragenter:当拖拽元素或选中的文本到一个可释放目标时触发; dragover:当元素或选中的文本被拖到一个可释放目标上时触发(每 100 毫秒触发一次); dragleave:当拖拽元素或选中的文本离开一个可释放目标时触发; drop:当元素或选中的文本在可释放目标上被释放时触发。 关于拖拽事件大家可以查看 mdn 官方文档笔者在这里就不细说了。
拖拽上传的核心是通过 DataTransfer 对象的 files 属性来获取文件列表,然后在利用 FormData 进行上传。核心代码 :
1 2 3 4 5 6 7 8 9 10 11 12 dropAreaEle.addEventListener ('drop' , handleDrop, false ) function handleDrop (e ) { const files = e.dataTransfer .files let formData = new FormData () Object .values (files).forEach ((file, i ) => { formData.append ('file' + i, file) }) }
复制粘贴上传 对于复制粘贴我们首先需要了解 Clipboard 对象。
我们可以通过 navigator.clipboard 来获取 Clipboard 对象,然后通过 navigator.clipboard.read()获取内容。但是对于不兼容的我们需要通过 e.clipboardData.items 来访问剪贴板中的内容。
下面的例子是获取剪切板里面的图片进行上传。
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 onst IMAGE_MIME_REGEX = /^image\/(jpe?g|gif|png)$/i ; const uploadAreaEle = document .querySelector ("#uploadArea" );uploadAreaEle.addEventListener ("paste" , async (e) => { e.preventDefault (); const files = []; if (navigator.clipboard ) { let clipboardItems = await navigator.clipboard .read (); for (const clipboardItem of clipboardItems) { for (const type of clipboardItem.types ) { if (IMAGE_MIME_REGEX .test (type)) { const blob = await clipboardItem.getType (type); files.push (blob); } } } } else { const items = e.clipboardData .items ; for (let i = 0 ; i < items.length ; i++) { if (IMAGE_MIME_REGEX .test (items[i].type )) { let file = items[i].getAsFile (); files.push (file); } } } let formData = new FormData (); files.forEach ((file, i ) => { formData.append ("file" + i, file); }); });
前面的上传都涉及到了FormData
,关于FormData
很多小伙伴可能不太理解,笔者在这里详细讲解下关于FormData
的相关api
。
FormData
我们可以想像成js
版的表单。功能和我们的html
表单是类似的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const formdata = new FormData ()formdata.append ('name' , 'randy' ) console .log (formdata.get ('name' )) formdata.set ('name' , 'demi' ) console .log (formdata.get ('name' )) formdata.getAll ('age' ) console .log (formdata.has ('name' )) formdata.delete ('name' )
除了创建一个全新的formData
,我们还可以基于一个现有表单进行初始化。
1 <form id ="myForm" > 名称:<input type ="text" name ="name" value ="randy" /> </form >
1 2 3 4 5 const myForm = document .querySelector ('#myForm' )const formdata = new FormData (myForm)console .log (formdata.get ('name' ))
对于formData
类似Object
,支持keys
、values
、entries
三种遍历方式
1 2 3 formData.keys () formData.values () formData.entries ()
了解更多,详见
阿里 oss 上传和下载 除了上面介绍的在自己服务器上传下载,我们还可能会碰到第三方服务器的上传和下载,例如oss
。
oss node 文档
相关链接 [1] 前端二进制 ArrayBuffer、TypedArray、DataView、Blob、File、Base64、FileReader 一次性搞清楚