记录--前端使用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' })// 字符串转ArrayBufferfunction 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) & 0xffreturn 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 = urlaLink.download = saveName + '.xlsx' || '1.xlsx' // HTML5新增的属性,指定保存文件名,可以不要后缀,注意,file:///模式下不会生效var eventif (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 = trueconst 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 线程内部,而不是主线程中。

  1. 不要直接依赖或者访问主线程的全局对象,比如 window、document 等。这些在 Worker 内都无法直接使用。

  2. 不要依赖 DOM 操作,比如获取某个 DOM 元素。Worker 线程无法访问页面的 DOM。

  3. 代码执行的入口是 onmessage 回调函数,在其中编写业务逻辑。

  4. 和主线程的通信只能通过 postMessage 和 onmessage 发送消息事件。

  5. 代码应该是自包含的,不依赖外部变量或状态。

  6. 可以导入其他脚本依赖,比如用 import 引入工具函数等。

  7. 避免修改或依赖全局作用域,比如定义全局变量等。

总之,编写 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.jsonmessage = 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

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/96019.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mac电脑屏幕录制Berrycast Mac屏幕录制软件

Berrycast是一款为Mac设计的优秀屏幕录制软件&#xff0c;它让屏幕录制变得简单而高效。以下是Berrycast的一些主要特点&#xff1a; 简单的用户界面&#xff1a;Berrycast拥有直观和简洁的用户界面&#xff0c;使得用户可以轻松上手。高质量的视频输出&#xff1a;Berrycast能…

虚拟机ping不通和在虚拟机里不能ping通百度时

一、出现的问题 1.finalshell连接虚拟机超时 2.ping: www.baidu.com: 未知的名称或服务 3.使用cmd无法ping通虚拟机的地址 二、解决方法 查看虚拟机设置 1.选择自定义VMnet8(NAT模式) 2.点击虚拟机虚拟网络编辑器 虚拟机的ip地址必须是192.168.123.xxx 如果不是 例如ip地…

区块链世界的大数据入门之zkMapReduce简介

1. 引言 跨链互操作性的未来将围绕多链dapp之间的动态和数据丰富的关系构建。Lagrange Labs 正在构建粘合剂&#xff0c;以帮助安全地扩展基于零知识证明的互操作性。 2. ZK大数据栈 Lagrange Labs 的ZK大数据栈 为一种专有的证明结构&#xff0c;用于在任意动态分布式计算的…

uni-app+uView实现点击查看大图片的效果

<u-button text"月落" click"imgPreview()"></u-button> //注意&#xff1a;参数urls 是预览图片的链接地址&#xff0c;是个数组 imgPreview() {uni.previewImage({indicator: "none",loop: false,urls: []&#xff0c;}) },参数说…

Python中的绝对和相对导入

在本文中&#xff0c;我们将看到Python中的绝对和相对导入。 Python中导入的工作 Python中的import类似于C/C中的#include header_file。Python模块可以通过使用import导入文件/函数来访问其他模块的代码。import语句是调用import机制的最常见方式&#xff0c;但它不是唯一的…

ORB-SLAM3复现过程中遇到的问题及解决办法

在复现过程中遇到的问题的解决过程 1. 版本检查1.1 Opencv版本的检测1.2 Eigen版本的检测1.3 查看Python版本1.4 其他 2. 编译过程中遇到的问题及解决办法2.1 ./build.sh遇到的问题2.2 ./build_ros.sh遇到的问题 因为环境比较干净&#xff0c;所以遇到的问题相对少一些&#xf…

Webgoat-Hijack a session通关答题教程

Webgoat-Hijack a session Hijack session是会话劫持&#xff0c;先了解需要用到的知识&#xff1a; Cookie原理&#xff1a; 1.客户端向服务端发起请求 2.服务端返回cookie&#xff0c;自己也保存了一份 3.客户端收到后&#xff0c;将cookie也保存起来 4.客户端再次发起请求时…

Linux服务器中创建SVN项目详细步骤

一、Linux服务器中的SVN安装和搭建项目环境可以参考一下文章: 1、《阿里云服务器搭建》------搭建SVN服务 2、在一个服务器的svn上&#xff0c;设置一个端口号对应一个项目 3、如何解决Linuxsvn无法显示日志的问题 二、Linux服务器中的SVN项目如何添加项目的忽略文件&#xff1…

Vue实现Antv/X6中的示例,以及一些er图开发场景

通过Vue实现Antv X6中的示例&#xff0c;以及一些开发场景&#xff0c;代码已经丢到仓库里了。 lwstudy/antv-x6-vue-demo: Vue实现Antv X6中的示例&#xff0c;以及一些开发场景 (github.com)learn-antv-x6: antv/X6学习 (gitee.com) 介绍 使用脚手架&#xff08;自动生成接…

QT实现任意阶贝塞尔曲线绘制

bezier曲线在编程中的难点在于求取曲线的系数&#xff0c;如果系数确定了那么就可以用微小的直线段画出曲线。bezier曲线的系数也就是bernstein系数&#xff0c;此系数的性质可以自行百度&#xff0c;我们在这里是利用bernstein系数的递推性质求取&#xff1a; 简单举例 两个…

基于JavaWeb和mysql实现校园订餐前后台管理系统(源码+数据库)

一、项目简介 本项目是一套基于JavaWeb和mysql实现网上书城前后端管理系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、项目文档、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都…

Ubuntu22.04安装Mongodb7.0

Ubuntu安装Mongodb 1.平台支持2.安装MongoDB社区版2.1导入包管理系统使用的公钥2.2为MongoDB创建列表文件2.3重新加载本地包数据库2.4安装MongoDB包1.安装最新版MongoDB2.安装指定版MongoDB 3.运行MongoDB社区版1.目录2.配置文件3.初始化系统4.启动MongoDB5.验证MongoDB是否成功…