木灵鱼儿
阅读:3134
目前前端下载文件的最佳实践
目前前端最佳的方案其实有两种:
- a元素下载
- iframe 无闪下载
事实上这两种下载方式,其实都存在相同的一个问题,就是如果下载的文件是跨域的(不是同域名下的问题,或者其他原因);并且这个文件浏览器可以直接打开的,比如:图片、文本文件。
那么浏览器会直接打开文件,并不是触发下载,而如果下载的文件是安装包,压缩包zip这些,则是触发下载。
这个问题就很头疼。
a元素下载的缺点
a元素下载,那么就必须设置download属性,这个属性可以接收一个参数,可以是带后缀的文件名,或者不带后缀。也可以为空,但是要设。
不带后缀的话文件格式就会与下载的文件保持一致,如果带了格式后缀,可能导致下载的jpg文件,你设置为xxx.png
,那么就会有格式上的问题。
而这个download其实是有兼容性的问题的,网传safari浏览器并不支持这个属性,其实就现在而言,其实是支持的,不支持的是ie。
然后就是文件预览的问题的,这个上面已经说明了。
iframe下载的缺点
iframe也会有预览的问题,但是没有兼容问题,由于可以预览,iframe一般都是display:none;
,所以即便预览了,我们也不知道,所以这种方式一般用于下载压缩包,安装包,浏览器无法直接预览的文件。
他不同于a链接,a链接的href属性不一定非得是外链,可以是BlobUrl和DataUrl。所以,a链接可以防止文件被预览,iframe则不行。
a链接如何防止文件被预览
两种方式:
- BlobUrl
- DataUrl
BlobUrl
BlobUrl就是通过请求,如ajax,或者fetch获取到文件对象,将其转为blob对象。
然后通过这个blob对象创建url,然后赋值给a链接即可。
//假设b等于blob对象
let a = document.createElement("a");
a.href = URL.createObjectURL(b);
a.download = fileName;
a.style.display = "none";
document.body.appendChild(a);
a.click();
URL.revokeObjectURL(a.href); // 释放URL 对象
a.remove();
这一套就完事了。
而且对于blob对象,和fetch合作简直就是天作之合,fetch可以直接将下载的文件转为blob。
function fileDownload(href, fileName = "") {
return new Promise((resolve, reject) => {
if (!href) return Promise.reject("herf文件下载地址不能为空");
fetch(href, {method: "GET",}).then(response => {
if (response.ok) {
return response.blob(); //转bolb
} else {
return reject(response.statusText);
}
}).then(blob => {
let a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = fileName;
a.style.display = "none";
document.body.appendChild(a);
a.click();
URL.revokeObjectURL(a.href); // 释放URL 对象
a.remove();
return resolve(true);
}).catch(err => {
return reject(err);
});
});
};
结合一下,万精油,啥文件都给你干下来。
DataUrl
DataUrl实际上就是base64,既然是base64,那么就绕不过canvas转base64,所以我就很烦这个,因为容易产生跨域问题,这个我暂时也没研究,因为感觉不太好用,就放一个别人的代码吧!
// ./util.js
// 图片转base64
function image2base64(img) {
const canvas = document.createElement("canvas");
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, img.width, img.height);
const mime = img.src.substring(img.src.lastIndexOf(".")+1).toLowerCase();
const dataUrl = canvas.toDataURL("image/" + mime);
return dataUrl;
}
// html页面,将a标签href属性动态赋值为dataUrl
<a id='downloadDataUrl' class="button is-dark">下载data:Url图片</a>
...
<script>
const image = new Image();
image.setAttribute("crossOrigin",'Anonymous');
image.src = '../files/test-download.png' + '?' + new Date().getTime();
image.onload = function() {
const imageDataUrl = image2base64(image);
const downloadDataUrlDom = document.getElementById('downloadDataUrl');
downloadDataUrlDom.setAttribute('href', imageDataUrl);
downloadDataUrlDom.setAttribute('download', 'download-data-url.png');
downloadDataUrlDom.addEventListener('click', () => {
console.log('下载文件');
});
}
</script>
使用这两种方式可以下载。iframe的也参考上面那个出处吧,等以后用到了我再贴代码。
第三方下载库 FileSaver
有现成的轮子不香吗????
这个库下载小文件可以,如果超大文件,超出了限制,就需要换个库了,具体看这个轮子的说明文档。
版权申明
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿 - 有梦就能远航站点。未经许可,禁止转载。
相关推荐
Promise失败重试,可指定重试次数
//模拟异步请求 function axiosFn() { return new Promise((resolve, reject) => { const flge = Math.random(); //随机值 setTimeout(() => { //大于0.7就是成功 if (flge > 0.7) { return resolve(flge); } else { return reject(...
animation 动画的三个事件
const div = document.querySelector("div.box"); div.addEventListener("animationstart", function() { //动画开始运行触发 }); div.addEventListener("animationiteration", function() { //动画每执行一次触发一次,适用用多次动画 }); div.addEventListener("animationend", function() { //...
手写Promise
/* * @Author: mulingyuer * @Date: 2021-12-30 22:06:58 * @LastEditTime: 2022-01-03 05:22:30 * @LastEditors: mulingyuer * @Description: 手写promise * @FilePath: \undefinedc:\Users\13219\Desktop\promise.js * 怎么可能会有bug!!! */ /** * @description: 自定义promise * @param {fucntion} executor 执行器函数(同...
深度合并对象的方法
找了很久,现有的库有两个:1. Mergenpm地址: Merge用法:import merge from 'merge'; const obj1 = { name: 2}; const obj2 = { value: 1 }; //合并 merge(obj1,obj2); console.log(obj1); //{name:2,value:1} //克隆合并-与目标对象无关联 const obj3 = merge(true,obj1,obj2); console.log(obj3); //{name:2,value:1} console.log(obj3 === obj1)...
利用JSON过滤对象和数组中指定的key属性
有时候我们在vue中进行for循环,就会涉及到绑定唯一值key的问题,但是并不是任何时候都会存在所谓的唯一值,使用index下标明显是不合适的,官方也不推荐,除非你for循环出来的列表不用变化。所以一般常用的做法就是给for循环的对象添加一个属性,属性的值是随机的uuid或者时间戳。这样前端问题解决了,如果遍历的数据还需要提交到后端,那么不就多了一个属性,这个属性后端不需要的。所以,我们需要在提交数据前,对数据进行过滤。过滤又得for循环删除?那怎么行,有没有那种通用的,简单的方法。过滤方法/** * @description: 过滤对象中指定的属性,也可以拿来浅拷贝 * @para...
Copy 一个复制操作的类
前言js有一个31k多的star的开源复制库:clipboard.js;但是一些简单复制并不想安装一个库来解决,所以就想自己写一个。copy所需要的东西Selection 对象用于获取被用户选中的部分,通过toString()方法可以获取被选中的文本内容,以及js操作选中。MDN文档:SelectionexecCommand 对象用于以命令的形式来操作网页的内容,说白了就是用它来实现复制文本操作,复制的是选中的文本MDN文档:execCommand 需要注意的是,execCommand在未来将会被遗弃,因为这个api本身是从ie浏览器那边继承的,久而久之各大浏览器都对其做了兼容,虽然...
对象扁平化
前言后端返回给前端的数据,有时候会是一个多层级对象,但是我们前端使用的时候,for循环遍历渲染时,多层级对象往往需要进行单独处理,因为还需要判断这个key值是否存在,否则会报错。强行让后端改变数据结构又好像不现实,无奈,只有自己处理了。掘金看到一位大佬文章《【算法】JS 实现对象的扁平化》感觉很合适,逻辑清晰。要求将对象中的层级扁平化,改成如下格式:// 实现一个 flatten 函数,实现如下的转换功能 const obj = { a: 1, b: [1, 2, { c: true }], c: { e: 2, f: 3 }, g: null, }; // 转换为 l...
javascript 生成随机密码,指定位数,难度(大小写、数字、特殊字符)
原理也没啥好藏着掖着的,就是为了保证密码难度,开头的字符一定是满足安全需要的,比如要求大小写数字加特殊字符,那么开头四位就一定是:大写一位、小写一位、数字一位、特殊字符一位;然后剩下的随机。/** * @description: 随机密码 * @param {*} len 密码位数 * @param {*} mode 密码难度:hide(大小写数字特殊字符)、medium(大小写数字)、low(小写数字) * @Date: 2021-07-02 15:52:32 * @Author: mulingyuer */ export const randomPass = functi...