Javascript 如何在生成文件以供下载时显示加载动画?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/29532788/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to display a loading animation while file is generated for download?
提问by Salketer
I have a web application where the user can generate PDF and PowerPoint files. These files may take some time to generate, so I would like to be able to display a loading animation while it generates. The problem here is that I have no mean to know when the download has started. The animation never goes away.
我有一个 Web 应用程序,用户可以在其中生成 PDF 和 PowerPoint 文件。这些文件可能需要一些时间来生成,所以我希望能够在生成时显示加载动画。这里的问题是我无法知道下载何时开始。动画永远不会消失。
I am aware that it could be possible to generate the file "on the side" and alert the user when the file is ready for download using AJAX, but I prefer "locking" the user while he waits for the download to start.
我知道可以在“侧面”生成文件并在文件准备好使用 AJAX 下载时提醒用户,但我更喜欢在用户等待下载开始时“锁定”用户。
回答by Salketer
To understand what needs to be done here, let's see what normally happens on this kind of request.
要了解这里需要做什么,让我们看看这种请求通常会发生什么。
User clicks the button to request the file.
The file takes time to generate (the user gets no feedback).
The file is finished and starts to be sent to user.
用户单击按钮以请求文件。
该文件需要时间来生成(用户没有得到反馈)。
文件完成并开始发送给用户。
What we would like to add is a feedback for the user to know what we are doing... Between step 1 and 2 we need to react to the click, and we need to find a way to detect when step 3 occurred to remove the visual feedback. We will not keep the user informed of the download status, their browser will do it as with any other download, we just want to tell the user that we are working on their request.
我们想要添加的是一个反馈,让用户知道我们在做什么……在第 1 步和第 2 步之间,我们需要对点击做出反应,我们需要找到一种方法来检测第 3 步何时发生以删除视觉反馈。我们不会让用户知道下载状态,他们的浏览器会像其他任何下载一样执行此操作,我们只是想告诉用户我们正在处理他们的请求。
For the file-generation script to communicate with our requester page's script we will be using cookies, this will assure that we are not browser dependent on events, iframes or the like. After testing multiple solutions this seemed to be the most stable from IE7 to latest mobiles.
为了使文件生成脚本与我们的请求者页面的脚本通信,我们将使用 cookie,这将确保我们不依赖于事件、iframe 等浏览器。在测试了多种解决方案后,这似乎是从 IE7 到最新手机最稳定的。
Step 1.5: Display graphical feedback.
步骤 1.5:显示图形反馈。
We will use javascript to display a notification on-screen. I've opted for a simple transparent black overlay on the whole page to prevent the user to interact with other elements of the page as following a link might make him loose the possibility to receive the file.
我们将使用 javascript 在屏幕上显示通知。我在整个页面上选择了一个简单的透明黑色覆盖层,以防止用户与页面的其他元素进行交互,因为跟随链接可能会使他失去接收文件的可能性。
$('#downloadLink').click(function() {
$('#fader').css('display', 'block');
});
#fader {
opacity: 0.5;
background: black;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
<div id="fader"></div>
<a href="#path-to-file-generator" id="downloadLink">Click me to receive file!</a>
</body>
Step 3.5: Removing the graphical display.
步骤 3.5:移除图形显示。
The easy part is done, now we need to notify javascript that the file is being downloaded. When a file is sent to the browser, it is sent with the usual HTTP headers, this allows us to update the client cookies. We will leverage this feature to provide the proper visual feedback. Let's modify the code above, we will need to set the cookie's starting value, and listen to it's modifications.
简单的部分已经完成,现在我们需要通知 javascript 文件正在下载。当一个文件被发送到浏览器时,它会与通常的 HTTP 标头一起发送,这允许我们更新客户端 cookie。我们将利用此功能提供适当的视觉反馈。让我们修改上面的代码,我们需要设置cookie的起始值,并监听它的修改。
var setCookie = function(name, value, expiracy) {
var exdate = new Date();
exdate.setTime(exdate.getTime() + expiracy * 1000);
var c_value = escape(value) + ((expiracy == null) ? "" : "; expires=" + exdate.toUTCString());
document.cookie = name + "=" + c_value + '; path=/';
};
var getCookie = function(name) {
var i, x, y, ARRcookies = document.cookie.split(";");
for (i = 0; i < ARRcookies.length; i++) {
x = ARRcookies[i].substr(0, ARRcookies[i].indexOf("="));
y = ARRcookies[i].substr(ARRcookies[i].indexOf("=") + 1);
x = x.replace(/^\s+|\s+$/g, "");
if (x == name) {
return y ? decodeURI(unescape(y.replace(/\+/g, ' '))) : y; //;//unescape(decodeURI(y));
}
}
};
$('#downloadLink').click(function() {
$('#fader').css('display', 'block');
setCookie('downloadStarted', 0, 100); //Expiration could be anything... As long as we reset the value
setTimeout(checkDownloadCookie, 1000); //Initiate the loop to check the cookie.
});
var downloadTimeout;
var checkDownloadCookie = function() {
if (getCookie("downloadStarted") == 1) {
setCookie("downloadStarted", "false", 100); //Expiration could be anything... As long as we reset the value
$('#fader').css('display', 'none');
} else {
downloadTimeout = setTimeout(checkDownloadCookie, 1000); //Re-run this function in 1 second.
}
};
#fader {
opacity: 0.5;
background: black;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
<div id="fader"></div>
<a href="#path-to-file-generator" id="downloadLink">Click me to receive file!</a>
</body>
Ok, what have we added here. I've put the set/getCookie functions I use, I don't know if they are the best, but they work very well. We set the cookie value to 0 when we initiate the download, this will make sure that any other past executions will not interfere. We also initiate a "timeout loop" to check the value of the cookie every second. This is the most arguable part of the code, using a timeout to loop function calls waiting for the cookie change to happen may not be the best, but it has been the easiest way to implement this on all browsers. So, every second we check the cookie value and, if the value is set to 1 we hide the faded visual effect.
好的,我们在这里添加了什么。我已经把我使用的set/getCookie函数,我不知道他们是不是最好的,但他们工作得很好。我们在启动下载时将 cookie 值设置为 0,这将确保任何其他过去的执行不会干扰。我们还启动了一个“超时循环”来每秒检查 cookie 的值。这是代码中最有争议的部分,使用超时来循环函数调用等待 cookie 更改可能不是最好的,但它是在所有浏览器上实现此功能的最简单方法。因此,每秒钟我们都会检查 cookie 值,如果该值设置为 1,我们就会隐藏褪色的视觉效果。
Changing the cookie server side
更改 cookie 服务器端
In PHP, one would do like so:
在 PHP 中,人们会这样做:
setCookie("downloadStarted", 1, time() + 20, '/', "", false, false);
In ASP.Net
在 ASP.Net 中
Response.Cookies.Add(new HttpCookie("downloadStarted", "1") { Expires = DateTime.Now.AddSeconds(20) });
Name of the cookie is downloadStarted, its value is 1, it expires in NOW + 20seconds(we check every second so 20 is more than enough for that, change this value if you change the timeout value in the javascript), its path is on the whole domain (change this to your liking), its not secured as it contains no sensitive data and it is NOT HTTP only so our javascript can see it.
cookie 的名称是downloadStarted,它的值是1,它会过期NOW + 20seconds(我们每秒检查一次,所以 20 就足够了,如果你在 javascript 中更改超时值,请更改此值),它的路径在整个域上(更改这是你喜欢的),它不安全,因为它不包含敏感数据,而且它不是 HTTP,所以我们的 javascript 可以看到它。
Voilà! That sums it up. Please note that the code provided works perfectly on a production application I am working with but might not suit your exact needs, correct it to your taste.
瞧!这就是总结。请注意,提供的代码在我正在使用的生产应用程序上完美运行,但可能不适合您的确切需求,请根据您的口味进行更正。
回答by Kidquick
This is a simplified version of Salketer's excellent answer. It simply checks for the existence of a cookie, without regard for its value.
这是 Salketer 优秀答案的简化版本。它只是检查 cookie 是否存在,而不考虑其值。
Upon form submit it will poll for the cookie's presence every second. If the cookie exists, the download is still being processed. If it doesn't, the download is complete. There is a 2 minute timeout.
提交表单后,它将每秒轮询 cookie 的存在。如果 cookie 存在,则下载仍在处理中。如果没有,则下载完成。有 2 分钟的超时时间。
The HTML/JS page:
HTML/JS 页面:
var downloadTimer; // reference to timer object
function startDownloadChecker(buttonId, imageId, timeout) {
var cookieName = "DownloadCompleteChecker";
var downloadTimerAttempts = timeout; // seconds
setCookie(cookieName, 0, downloadTimerAttempts);
// set timer to check for cookie every second
downloadTimer = window.setInterval(function () {
var cookie = getCookie(cookieName);
// if cookie doesn't exist, or attempts have expired, re-enable form
if ((typeof cookie === 'undefined') || (downloadTimerAttempts == 0)) {
$("#" + buttonId).removeAttr("disabled");
$("#" + imageId).hide();
window.clearInterval(downloadTimer);
expireCookie(cookieName);
}
downloadTimerAttempts--;
}, 1000);
}
// form submit event
$("#btnSubmit").click(function () {
$(this).attr("disabled", "disabled"); // disable form submit button
$("#imgLoading").show(); // show loading animation
startDownloadChecker("btnSubmit", "imgLoading", 120);
});
<form method="post">
...fields...
<button id="btnSubmit">Submit</button>
<img id="imgLoading" src="spinner.gif" style="display:none" />
</form>
Supporting Javascript to set/get/delete cookies:
支持 Javascript 设置/获取/删除 cookie:
function setCookie(name, value, expiresInSeconds) {
var exdate = new Date();
exdate.setTime(exdate.getTime() + expiresInSeconds * 1000);
var c_value = escape(value) + ((expiresInSeconds == null) ? "" : "; expires=" + exdate.toUTCString());
document.cookie = name + "=" + c_value + '; path=/';
};
function getCookie(name) {
var parts = document.cookie.split(name + "=");
if (parts.length == 2) return parts.pop().split(";").shift();
}
function expireCookie(name) {
document.cookie = encodeURIComponent(name) + "=; path=/; expires=" + new Date(0).toUTCString();
}
Server side code in ASP.Net:
ASP.Net 中的服务器端代码:
...generate big document...
// attach expired cookie to response to signal download is complete
var cookie = new HttpCookie("DownloadCompleteChecker"); // same cookie name as above!
cookie.Expires = DateTime.Now.AddDays(-1d); // expires yesterday
HttpContext.Current.Response.Cookies.Add(cookie); // Add cookie to response headers
HttpContext.Current.Response.Flush(); // send response
Hope that helps! :)
希望有帮助!:)
回答by jcubic
You can fetch the file using ajax add indicator then create a tag with dataURI and click on it using JavaScript:
您可以使用 ajax add indicator 获取文件,然后使用 dataURI 创建一个标签并使用 JavaScript 单击它:
You will need help from this lib: https://github.com/henrya/js-jquery/tree/master/BinaryTransport
您将需要此库的帮助:https: //github.com/henrya/js-jquery/tree/master/BinaryTransport
var link = document.createElement('a');
if (link.download != undefined) {
$('.download').each(function() {
var self = $(this);
self.click(function() {
$('.indicator').show();
var href = self.attr('href');
$.get(href, function(file) {
var dataURI = 'data:application/octet-stream;base64,' + btoa(file);
var fname = self.data('filename');
$('<a>' + fname +'</a>').attr({
download: fname,
href: dataURI
})[0].click();
$('.indicator').hide();
}, 'binary');
return false;
});
});
}
You can see download attribute support on caniuse
你可以在caniuse上看到下载属性支持
and in your html put this:
并在您的 html 中输入:
<a href="somescript.php" class="download" data-filename="foo.pdf">generate</a>

