admin管理员组文章数量:1644382
简介
前端的上传下载平时不会经常用到,就算用到可能也是前人已经写好的模块或者是第三方库,引入就可以使用了。但是笔者觉得作为前端开发,文件的上传和下载还是非常有必要了解清楚的。
本文主要讲述前端文件上传和下载,这里可能会涉及到前端的一些二进制,例如ArrayBuffer、TypedArray、DataView、Blob、File、Base64、FileReader
等,如果对这些不清楚的话,强烈建议可以先看看笔者写的前端二进制一次性搞清楚文章,这样会让你阅读更舒畅些。
下载
对于下载,方式有很多种,而且每种方式都有各自的特点。
超链接下载
使用超链接来完成我们前端的下载是最常见的一种方式。
利用超链接下载分两种情况,第一种下载本地资源,我们给超链接加上download
属性就可以啦。
比如下面的例子,下载本地的logo
图片,然后命名为test
。这样下载下来的图片就是test.png
啦。
<a href="./imgs/logo.png" download="test">直接下载图片</a>
download
属性用来给下载的文件命名的,但是需要注意download
属性的兼容性。
接下来我们重点来说第二种,第二种就是下载服务器端的资源,这就需要用到blob url或者base64数据啦。
对于blob url
,我们可以使用window.URL.createObjectURL(blob)
方法生成 blob url
,然后将blob url
赋值给超链接的href
属性,然后模拟点击超链接进行下载。
blob url
,简单的理解一下就是将一个file
或Blob
类型的对象转为UTF-16
的字符串,并保存在当前操作的document
下,存储在内存中。
生成blob url
使用的方法是URL.createObjectURL(file/blob)
。清除方式只有页面unload()
事件或者使用URL.revokeObjectURL(objectURL)
手动清除 。
对于base64数据
,我们可以直接把base64数据
赋值给超链接的href
属性,然后模拟点击超链接进行下载。
下面我将模拟后端返回ArrayBuffer
、Blob
对象和base64数据
来实现超链接方式下载。
后端返回ArrayBuffer
因为生成blob url
的参数只能是blob
或file
对象,所以对于后端返回的ArrayBuffer
我们需要先转成blob
或者file
对象然后再生成blob url
。
function aDownload1() {
// 模拟后端返回 ArrayBuffer
const str = "hello randy!";
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");
// 设置文件名为test
a.download = "test";
// 将 ArrayBuffer 转成blob,这里也可以转成file对象。
const blob = new Blob([ia], { type: "text/plain" });
// 生成blob url。这里可以使用Blob对象或者File对象
a.href = window.URL.createObjectURL(blob);
a.style.display = "none";
document.body.appendChild(a);
a.click();
// 释放内存
window.URL.revokeObjectURL(a.href);
// 移除a元素
document.body.removeChild(a);
}
后端返回Blob
对于后端返回blob
,我们就不需要再转换了,直接生成blob url
就可以了。
function aDownload2() {
// 模拟后端返回 Blob
const blob = new Blob(["hello", "randy"], { type: "text/plain" });
const a = document.createElement("a");
// 设置文件名为test
a.download = "test";
// 直接生成blob url。这里可以使用Blob对象或者File对象
a.href = window.URL.createObjectURL(blob);
a.style.display = "none";
document.body.appendChild(a);
a.click();
// 释放内存
window.URL.revokeObjectURL(a.href);
// 移除a元素
document.body.removeChild(a);
}
后端返回base64
后端返回base64
的情况还是不多,但是我们也是需要知道的。
async function aDownload3() {
// 模拟后端返回 Base64,这里不理解的可以看看笔者前面写的二进制文章
// 这里就是将本地的图片转为base64
const b1 = await img2base64("./imgs/logo.png");
const a = document.createElement("a");
a.download = "test";
// 给超链接赋值Base64也是可以的。
a.href = b1;
a.style.display = "none";
document.body.appendChild(a);
a.click();
// 移除超链接
document.body.removeChild(a);
}
showSaveFilePicker API 下载
showSaveFilePicker 是一个新的api
,调用该方法后会显示允许用户选择保存路径的文件选择器。
const FileSystemFileHandle = Window.showSaveFilePicker(options);
showSaveFilePicker 方法支持一个对象类型的可选参数,可包含以下属性:
-
excludeAcceptAllOption
:布尔类型,默认值为false
。默认情况下,选择器应包含一个不应用任何文件类型过滤器的选项(由下面的types
选项启用)。将此选项设置为true
意味着types
选项不可用。 -
types
:数组类型,表示允许保存的文件类型列表。数组中的每一项是包含以下属性的配置对象:
description(可选)
:用于描述允许保存文件类型类别。accept
:是一个对象,该对象的key
是 MIME 类型,值是文件扩展名列表。
suggestedName
建议的文件名。
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() {
// 模拟blob文件
const blob = new Blob(["hello", "randy"], { type: "text/plain" });
download3(blob, "test.txt");
}
当你点击下载后会出现如下选择界面,会出现你建议的文件名和文件类型选择。
相比 a 标签下载 的方式,showSaveFilePicker API
允许你选择文件的下载目录、选择文件的保存格式和更改存储的文件名称。不过可惜的是该 API
目前的兼容性还不是很好。
注意这种下载方式下载的文件不会出现在浏览器下载列表哦。
FileSaver 下载
FileSaver.js是在客户端保存文件的解决方案,非常适合在客户端上生成文件的 Web 应用程序。
兼容性如下
对于FileSaver.js
我们主要需要记住他的这个saveAs
方法。
FileSaver.saveAs(
Blob/File/Url,
optional DOMString filename,
optional Object { autoBom }
)
saveAs
方法支持 3 个参数:
第 1 个参数表示它支持 Blob/File/Url
三种类型。
第 2 个参数表示文件名(可选)。
而第 3 个参数表示配置对象(可选)。如果你需要 FlieSaver.js
自动提供 Unicode
文本编码提示字节顺序标记,则需要设置 { autoBom: true}
。请注意,只有当blob
类型的charset=utf-8
设置时,才能执行此操作。
安装
npm install file-saver --save
blob
数据源
const blob = new Blob(["Hello, world!"], {type: "text/plain;charset=utf-8"});
FileSaver.saveAs(blob, "helloworld.txt");
const canvas = document.getElementById("my-canvas");
canvas.toBlob(function(blob) {
saveAs(blob, "prettyimage.png");
});
file
数据源
const file = new File(["Hello, world!"], {type: "text/plain;charset=utf-8"});
FileSaver.saveAs(file, "helloworld.txt");
网络链接数据源
FileSaver.saveAs("https://httpbin/image", "image.jpg");
jszip 压缩下载
jszip可以让下载的文件转为zip
格式。
jszip
自己不具备下载功能,他只是提供了将文件压缩成zip
包的功能,下载的话我们还是需要借助前面所说的FileSaver.js
。
安装
npm install jszip
使用
// 创建 JSZip 对象
var zip = new JSZip();
// 把文件添加到前面创建的 JSZip 对象中,可以添加多个
zip.file("Hello.txt", "Hello World\n");
// 添加第二个文件,文件是blob对象
zip.file("Hello2.txt", blob);
// 添加第二个文件,文件是file对象
zip.file("Hello3.txt", file);
// 生成 JSZip 文件
zip.generateAsync({type:"blob"}).then(function(content) {
// 这里需要用到上面说的 FileSaver.js
FileSaver.saveAs(content, "example.zip");
});
关于jszip
更多的使用方法可以查看官方文档
附件形式下载
说到附件下载很多小伙伴可能不太懂,下面笔者举个例子你就会明白了。
我们平时在浏览器输入图片链接地址,为什么有的图片是预览而有的却是直接下载呢?
这个链接在浏览器打开是直接预览https://p9-juejin.byteimg/tos-cn-i-k3u1fbpfcp/ab5e763e993f4bfab3c2ce3ef2935ec6~tplv-k3u1fbpfcp-watermark.image
这个链接在浏览器打开是直接下载http://pic.96weixin/ueditor/20200511/1589185236200665.jpg?x-oss-process=image/resize,m_lfit,w_120
这里就涉及到附件形式下载了。
我们可以通过设置 Content-Disposition
响应头来指示响应的内容以何种形式展示,是以内联(inline)的形式,还是以附件(attachment)的形式下载并保存到本地。filename
用来设置下载的文件的文件名。
Content-Disposition: inline
Content-Disposition: attachment
Content-Disposition: attachment; filename="mouth.png"
打开控制台可以发现,我们上面的第二张图就是设置了Content-Disposition
响应头,所以我们输入完图片链接后会以附件的形式直接下载。
上传
在上传这一块,不管使用何种方式,都是先获取到文件对象然后在利用表单FormData
对象进行传输。
单文件上传
<input id="uploadFile1" type="file" accept="image/*" />
const upload = () => {
// 获取上传的input元素
const uploadFileEle = document.querySelector("#uploadFile1");
// 获取文件
const files = uploadFileEle.files;
let formData = new FormData();
formData.append(fieldName, files[0]);
// 进行请求
// axios.post(url, formData)
}
多文件上传
对于多文件上传,我们只需要稍微改一下。在input
元素里面添加multiple
属性,表示支持多文件上传。
<input id="uploadFile2" type="file" accept="image/*" multiple />
对于js,我们需要在表单里面循环添加我们的文件。
const upload = () => {
// 获取上传的input元素
const uploadFileEle = document.querySelector("#uploadFile2");
// 获取文件
const files = uploadFileEle.files;
let formData = new FormData();
Object.values(files).forEach((file, i) => {
formData.append('file' + i, file);
});
// 进行请求
// axios.post(url, formData)
}
文件夹上传
对于文件夹上传,我们只需要稍微改一下。在input
元素里面添加webkitdirectory
属性,表示是文件夹上传。
该属性的兼容性如下,需要注意IE
是完全不支持的。
<input id="uploadFile3" type="file" accept="image/*" webkitdirectory />
对于js,我们需要在表单里面循环添加我们的文件。
const upload = () => {
// 获取上传的input元素
const uploadFileEle = document.querySelector("#uploadFile3");
// 获取文件
const files = uploadFileEle.files;
let formData = new FormData();
Object.values(files).forEach((file, i) => {
formData.append('file' + i, file);
});
// 进行请求
// axios.post(url, formData)
}
以文件夹方式上传的话,在选择文件夹后会有个小提示,如下
并且我们可以在File
对象里面通过webkitRelativePath
属性看到该文件的相对路径。
jszip 压缩上传
压缩上传就是将文件压缩成压缩包,然后再上传到服务端。压缩还是使用我们前面介绍的jszip库。
下面我用文件夹上传的方式举例。
<input id="uploadFile4" type="file" accept="image/*" webkitdirectory />
function generateZipFile(
zipName,
files,
options = { type: "blob", compression: "DEFLATE" }
) {
return new Promise((resolve, reject) => {
// 创建 JSZip 对象
const zip = new JSZip();
Object.values(files).forEach((file, i) => {
// 循环遍历 把文件添加到前面创建的 JSZip 对象中
zip.file("file" + i, file);
});
// 生成 JSZip 文件
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() {
// 获取上传的input元素
const uploadFileEle = document.querySelector("#uploadFile4");
// 获取文件
const files = uploadFileEle.files;
// 获取相对路径
let webkitRelativePath = fileList[0].webkitRelativePath;
// 获取文件夹的名字,用做zip包的名字
let zipFileName = webkitRelativePath.split("/")[0] + ".zip";
let zipFile = await generateZipFile(zipFileName, fileList);
let formData = new FormData();
formData.append('zipfile', zipFile);
// 进行请求
// axios.post(url, formData)
}
拖拽上传
要实现拖拽上传的功能,我们需要先了解与拖拽相关的事件。比如 drag
、dragend
、dragenter
、dragover
或 drop
事件等。
dragenter
:当拖拽元素或选中的文本到一个可释放目标时触发;dragover
:当元素或选中的文本被拖到一个可释放目标上时触发(每100毫秒触发一次);dragleave
:当拖拽元素或选中的文本离开一个可释放目标时触发;drop
:当元素或选中的文本在可释放目标上被释放时触发。
关于拖拽事件大家可以查看mdn 官方文档笔者在这里就不细说了。
拖拽上传的核心是通过 DataTransfer 对象的 files
属性来获取文件列表,然后在利用FormData
进行上传。
核心代码
dropAreaEle.addEventListener("drop", handleDrop, false);
function handleDrop(e) {
// 在dataTransfer对象上获取文件列表
const files = e.dataTransfer.files;
let formData = new FormData();
Object.values(files).forEach((file, i) => {
formData.append("file" + i, file);
});
// 进行请求
// axios.post(url, formData)
}
复制粘贴上传
对于复制粘贴我们首先需要了解Clipboard对象。
我们可以通过 navigator.clipboard
来获取 Clipboard
对象,然后通过navigator.clipboard.read()
获取内容。但是对于不兼容的我们需要通过 e.clipboardData.items
来访问剪贴板中的内容。
下面的例子是获取剪切板里面的图片进行上传。
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);
}
}
}
// 有了files我们就可以利用FormData进行上传啦
let formData = new FormData();
files.forEach((file, i) => {
formData.append("file" + i, file);
});
// 进行请求
// axios.post(url, formData)
});
关于FormData
前面的上传都涉及到了FormData
,关于FormData
很多小伙伴可能不太理解,笔者在这里详细讲解下关于FormData
的相关api
。
FormData
我们可以想像成js
版的表单。功能和我们的html
表单是类似的。
// 通过FormData构造函数创建一个空对象
const formdata = new FormData();
// 可以通过append()方法来追加数据
formdata.append("name","randy");
// 通过get方法对值进行读取
console.log(formdata.get("name"));//randy
// 通过set方法对值进行设置
formdata.set("name","demi");
console.log(formdata.get("name"));//demi
// 获取key为age的所有值,返回值为数组类型
formdata.getAll("age");
// 判断是否包含key为name的数据
console.log(formdata.has("name"));//true
// 删除key为name的值
formdata.delete("name");
除了创建一个全新的formData
,我们还可以基于一个现有表单进行初始化。
<form id="myForm">
名称:<input type="text" name="name" value="randy">
</form>
// 根据id获得页面当中的form表单元素
const myForm = document.querySelector("#myForm");
// 将获得的表单元素作为参数,对formData进行初始化
const formdata=new FormData(myForm);
console.log(formdata.get("name"));// randy
对于formData
类似Object
,支持keys
、values
、entries
三种遍历方式
formData.keys();
formData.values();
formData.entries();
关于formData
更多细节可以自行查看mdn 官方文档
阿里 oss上传和下载
除了上面笔者介绍的在自己服务器上传下载,我们还可能会碰到第三方服务器的上传和下载,例如oss
,这个我们也是需要了解的。对于我们前端来说着重看 oss node文档就可以了。
后记
本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力。
版权声明:本文标题:前端文件的上传和下载大全(总有一种适合你) 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/xitong/1729383953a1199299.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论