记录--前端使用a链接下载内容增加loading效果
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
问题描述:最近工作中出现一个需求,纯前端下载 Excel 数据,并且有的下载内容很多,这时需要给下载增加一个 loading 效果。
代码如下:
// utils.js
const XLSX = require('xlsx')
// 将一个sheet转成最终的excel文件的blob对象,然后利用URL.createObjectURL下载
export const sheet2blob = (sheet, sheetName) => {
sheetName = sheetName || 'sheet1'
var workbook = {
SheetNames: [sheetName],
Sheets: {}
}
workbook.Sheets[sheetName] = sheet
// 生成excel的配置项
var wopts = {
bookType: 'xlsx', // 要生成的文件类型
bookSST: false, // 是否生成Shared String Table,官方解释是,如果开启生成速度会下降,但在低版本IOS设备上有更好的兼容性
type: 'binary'
}
var wbout = XLSX.write(workbook, wopts)
var blob = new Blob([s2ab(wbout)], { type: 'application/octet-stream' })
// 字符串转ArrayBuffer
function s2ab(s) {
var buf = new ArrayBuffer(s.length)
var view = new Uint8Array(buf)
for (var i = 0; i !== s.length; ++i) view[i] = s.charCodeAt(i) & 0xff
return buf
}
return blob
} /**
* 通用的打开下载对话框方法,没有测试过具体兼容性
* @param url 下载地址,也可以是一个blob对象,必选
* @param saveName 保存文件名,可选
*/
export const openDownloadDialog = (url, saveName) => {
if (typeof url === 'object' && url instanceof Blob) {
url = URL.createObjectURL(url) // 创建blob地址
}
var aLink = document.createElement('a')
aLink.href = url
aLink.download = saveName + '.xlsx' || '1.xlsx' // HTML5新增的属性,指定保存文件名,可以不要后缀,注意,file:///模式下不会生效
var event
if (window.MouseEvent) event = new MouseEvent('click')
else {
event = document.createEvent('MouseEvents')
event.initMouseEvent(
'click',
true,
false,
window,
0,
0,
0,
0,
0,
false,
false,
false,
false,
0,
null
)
}
aLink.dispatchEvent(event)
} <el-button
@click="clickExportBtn"
>
<i class="el-icon-download"></i>下载数据
</el-button>
<div class="mongolia" v-if="loadingSummaryData">
<el-icon class="el-icon-loading loading-icon">
<Loading />
</el-icon>
<p>loading...</p>
</div> clickExportBtn: _.throttle(async function() {
const downloadDatas = []
const summaryDataForDownloads = this.optimizeHPPCDownload(this.summaryDataForDownloads)
summaryDataForDownloads.map(summaryItem =>
downloadDatas.push(this.parseSummaryDataToBlobData(summaryItem))
)
// donwloadDatas 数组是一个三维数组,而 json2sheet 需要的数据是一个二维数组
this.loadingSummaryData = true
const downloadBlob = aoa2sheet(downloadDatas.flat(1))
openDownloadDialog(downloadBlob, `${this.testItem}报告数据`)
this.loadingSummaryData = false
}, 2000), // css
.mongolia {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.9);
display: flex;
justify-content: center;
align-items: center;
font-size: 1.5rem;
color: #409eff;
z-index: 9999;
}
.loading-icon {
color: #409eff;
font-size: 32px;
}
解决方案探究:
在尝试了使用 $nextTick、将 openDownloadDialog 改写成 Promise 异步函数,或者使用 async/await、在 openDownloadDialog 中添加 loadingSummaryData 逻辑,发现依旧无法解决问题,因此怀疑是 document 添加新元素与 vue 的 v-if 渲染产生冲突,即 document 添加新元素会阻塞 v-if 的执性。查阅资料发现,问题可能有以下几种:
openDownloadDialog 在执行过程中执行了较为耗时的同步操作,阻塞了主线程,导致了页面渲染的停滞。
openDownloadDialog 的 click 事件出发逻辑存在问题,阻塞了事件循环(Event Loop)。
浏览器在执行 openDownloadDialog 时,将其脚本任务的优先级设置得较高,导致占用主线程时间片,推迟了其他渲染任务。
Vue 的批量更新策略导致了 v-if 内容的显示被延迟。
查阅资料后找到了如下几种方案:
1.使用 setTimeout 使 openDownloadDialog 异步执行
clickExport() {
this.loadingSummaryData = true;
setTimeout(() => {
openDownloadDialog(downloadBlob, `${this.testItem}报告数据`);
this.loadingSummaryData = false;
});
}
2.对 openDownloadDialog 内部进行优化
避免大循环或递归逻辑
将计算工作分批进行
使用 Web Worker 隔离耗时任务
在编写 downloadWorker.js 中的代码时,要明确这部分代码是运行在一个独立的 Worker 线程内部,而不是主线程中。
不要直接依赖或者访问主线程的全局对象,比如 window、document 等。这些在 Worker 内都无法直接使用。
不要依赖 DOM 操作,比如获取某个 DOM 元素。Worker 线程无法访问页面的 DOM。
代码执行的入口是 onmessage 回调函数,在其中编写业务逻辑。
和主线程的通信只能通过 postMessage 和 onmessage 发送消息事件。
代码应该是自包含的,不依赖外部变量或状态。
可以导入其他脚本依赖,比如用 import 引入工具函数等。
避免修改或依赖全局作用域,比如定义全局变量等。
总之,编写 downloadWorker.js 时要注意它执行在一个独立环境中,只能通过消息事件与主线程通信。
// 创建 Worker
const worker = new Worker('downloadWorker.js'); // 点击下载时向 Worker 发送消息
function clickDownload() { showLoading(); worker.postMessage({
url: fileURL,
filename: 'report.xlsx'
}); worker.onmessage = function(e) {
// 收到下载完成的消息
hideLoading();
} } // 显示 loading
function showLoading() {
loading.style.display = 'block';
} // 隐藏 loading
function hideLoading() {
loading.style.display = 'none';
} // downloadWorker.js onmessage = function(e) {
const url = e.data.url;
const filename = e.data.filename; // 创建并点击链接触发下载
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click(); postMessage('下载完成');
} <div id="loading" style="display:none;">
Downloading...
</div> <button onclick="clickDownload()">
Download
</button> <script src="downloadWorker.js"></script>
3.使用 requestIdleCallback 进行调度
clickExport() {
this.loadingSummaryData = true;
requestIdleCallback(() => {
openDownloadDialog(downloadBlob, `${this.testItem}报告数据`);
this.loadingSummaryData = false;
});
}
当使用上述方案时发现里面的内容变得异常的慢,于是去问GPT后,得到以下答案:
使用 requestIdleCallback 来执行 openDownloadDialog 确实可能会需要更多时间。
因为 requestIdleCallback 会在主线程空闲时才执行回调函数,所以不像 setTimeout 可以直接按指定时间异步执行。
requestIdleCallback 的优先级较低,只在主线程空闲时才会执行,所以 download 的速度会比 setTimeout 慢一些。
但使用 requestIdleCallback 的优点是它不会占用主线程时间,可以避免阻塞页面渲染。
所以需要权衡执行速度和避免阻塞之间的关系:
如果下载速度是优先考虑的因素,可以继续使用 setTimeout 异步执行。
如果避免阻塞渲染更重要,则 requestIdleCallback 是更好的选择。
偶尔下载任务被延迟不是大问题,但频繁的下载任务阻塞会严重影响页面体验。
此外,可以结合两者试试:
clickExport() {
this.loadingSummaryData = true;
setTimeout(() => {
requestIdleCallback(() => {
openDownloadDialog(downloadBlob);
});
this.loadingSummaryData = false;
}, 200);
}
setTimeout 先异步调度,然后在回调中使用 requestIdleCallback 避免阻塞。这样就可以兼顾下载速度和避免渲染阻塞了。
分析线程模型,找到具体的阻塞点
使用 Performance 工具分析线程
debugger 及 console.log 打印关键函数的执行时间
检查是否有非主线程的任务阻塞了主线程
调整 vue 的批量更新策略
new Vue({
config: {
// 修改批量更新的队列长度
batchUpdateDuration: 100
}
})
本文转载于:
https://juejin.cn/post/7268050036474609683
如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

记录--前端使用a链接下载内容增加loading效果的更多相关文章
- vue 首页背景图片加载完成前增加 loading 效果 -- 使用 new Image() 实现
1. 创建 loading 公用组件 <template> <div class="load-container"> <div class=" ...
- 前端vue项目-关于下载文件pdf/excel(三)
最近在做一些需求,需要下载一些文件信息,最频繁的就是下载excel文件到本地了 看过了很多方法,做个整理吧哈哈哈哈 参考的文章链接: https://www.cnblogs.com/jiangweic ...
- LFS(Linux From Scratch)构建过程全记录(三):下载所需的软件包
写在前面 本文将记录构建LFS的过程中,下载软件包的全过程 准备下载的路径 注意请确保$LFS已经设置完毕 我们需要创建一个文件夹,地址为$LFS/sources,用于保存对应的源码 输入的指令如下: ...
- CozyRSS开发记录15-获取和显示RSS内容
CozyRSS开发记录15-获取和显示RSS内容 1.内容列表 我们先给RSSContentFrame增加一个ViewModel,里面和RSS源列表一样,提供一个ObservableCollectio ...
- EasyUI form ajax submit到MVC后,在IE下提示下载内容的解决办法
问题描述: 项目环境为,.Net Mvc5+EF6……前端框架使用的是EasyUI v1.4.4. 在视图页面中,使用form的submit方法提交表单数据时,如果是使用IE的话,请求成功后IE会提示 ...
- API例子:用Java/JavaScript下载内容提取器
1,引言 本文讲解怎样用Java和JavaScript使用 GooSeeker API 接口下载内容提取器,这是一个示例程序.什么是内容提取器?为什么用这种方式?源自Python即时网络爬虫开源项目: ...
- Rancher 容器管理平台-免费视频培训-链接及内容-第三季
Rancher 容器管理平台-免费视频培训-链接及内容 第三季 第5期-2018年05月10日-持续集成的容器化实践回放网址:http://www.itdks.com/liveevent/detail ...
- (转)libcurl应用:如何把下载内容写入内存
libcurl应用:如何把下载内容写入内存 2008-01-13 00:32:52| 分类: 默认分类 |举报 |字号 订阅 libcurl的文档中有 getinmemory.c这个例子,把下载 ...
- Oracle Length 和 Lengthb 函数说明 .(用来判断记录值里是否有中文内容)
一.官网的说明 http://download.oracle.com/docs/cd/E11882_01/server.112/e26088/functions088.htm#SQLRF00658 P ...
- Asp.net多行文本框随内容增加而高度增加
最近做一个项目,在一个多行文本框(TextBox)里显示新闻内容,由于内容有多有少,并且总是出现垂直滚动条,很不好看,笔者就想用一个法子,去掉垂直滚动条,并且文本框的高度随内容的增加而变大,使高度适应 ...
随机推荐
- SVC服务的发现
目录 服务的发现 发现的方式 1. ClusterIP 2. 通过变量 3. 通过DNS(推荐) 服务的发现 发现的方式 服务的发现说的就是svc的ip只有集群内的资源可以访问到,比如集群内的节点,p ...
- Swoole从入门到入土(25)——多进程[进程间无锁计数器]
Atomic 是 Swoole 底层提供的原子计数操作类,可以方便整数的无锁原子增减.原子计数器有如下特点: - 使用共享内存,可以在不同的进程之间操作计数 - 基于 gcc/clang 提供的 CP ...
- LVM精简卷(Thinly-Provisioned Logical Volumes)
可能LVM大家都比较熟悉,那么精简卷又是干什么的呢?相比于普通LVM有什么优势,又会带来哪些新的问题?带着这些我们来一探究竟: 工作原理 在创建Thin"瘦"卷时,预分配一个虚拟的 ...
- Excelize 开源基础发布 2.8.1 版本,2024 年首个更新
Excelize 是 Go 语言编写的用于操作电子表格办公文档的开源基础库,基于 ISO/IEC 29500.ECMA-376 国际标准.可以使用它来读取.写入由 Microsoft Excel.WP ...
- Qt开发技术:QCharts(四)QChart面积图介绍、Demo以及代码详解
若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...
- context讲解
context包 context包介绍 在go语言中,每个独立调用一般都会被单独的协程处理.但在处理一个请求时,往往可能需要在多个协程之间进行信息传递,甚至包括一层层地递进顺序传递,而且这种信息往 ...
- 项目打包setup.py(setuptools)
参考 https://www.cnblogs.com/dan-baishucaizi/p/13564333.html https://www.cnblogs.com/dan-baishucaizi/p ...
- 逆向实战31——xhs—xs算法分析
前言 本文章中所有内容仅供学习交流,抓包内容.敏感网址.数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除! 公众号链接 目标网站 aH ...
- Java 通过属性名称读取或者设置实体的属性值
原因 项目实战中有这个需求,数据库中配置对应的实体和属性名称,在代码中通过属性名称获取实体的对应的属性值. 解决方案 工具类,下面这个工具是辅助获取属性值 import com.alibaba.fas ...
- Android底层渲染原理
Overview多年前Android的UI流畅性差的问题一直饱受诟病,Google为了解决这个问题开发了Project Butter项目,也就是黄油计划,期望彻底改善Android系统的流畅性.这是A ...
