使用window.location.href下载文件时加载动画不生效问题排查
window.location.href Downloads Let's break down exactly why your loader isn't showing up here, and how to fix it:
The core problem is timing. When you use window.location.href to trigger a file download, your code runs in a strict synchronous sequence:
- You call
indicatorStart()to add the loader to the DOM - You tell the browser to fetch the file via
window.location.href - Immediately after, you call
indicatorStop()to remove the loader
The browser doesn't get a chance to actually render the loader before it's deleted. Unlike AJAX requests (which are asynchronous, giving the browser time to paint the loader while waiting for a response), this entire sequence happens in a split second. Even if you didn't delete the loader right away, the browser might temporarily block DOM rendering while handling the file download request, so the loader still wouldn't appear.
1. Use Asynchronous Fetch + Blob Download (Most Reliable)
This approach gives you full control over the loader's lifecycle, and even lets you handle download errors gracefully. We'll fetch the file as a Blob, then create a temporary link to trigger the download:
$(document) .off('click', '#btnExportToExcel') .on('click', '#btnExportToExcel', async function (e) { e.preventDefault(); indicatorStart('Loading'); try { // Fetch the file asynchronously const response = await fetch('/Index/ExcelReport'); if (!response.ok) throw new Error('Failed to load report'); // Convert the response to a Blob const blob = await response.blob(); // Create a temporary download link const downloadUrl = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = downloadUrl; // Extract filename from backend's Content-Disposition header (if available) const contentDisposition = response.headers.get('Content-Disposition'); if (contentDisposition) { const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/); if (filenameMatch) { link.download = filenameMatch[1].replace(/['"]/g, ''); } } // Trigger the download document.body.appendChild(link); link.click(); // Clean up temporary resources window.URL.revokeObjectURL(downloadUrl); document.body.removeChild(link); } catch (error) { console.error('Download failed:', error); // Add custom error handling here (e.g., show a user-friendly toast) } finally { // Stop the loader once download completes (or fails) indicatorStop(); } });
Pros:
- Loader stays visible until the download finishes (or fails)
- Supports error handling for failed requests
- Works consistently across all modern browsers
2. Add a Short Delay to indicatorStop() (Quick & Simple)
If you want to avoid rewriting your download logic, you can add a small timeout to give the browser time to render the loader before removing it. Note this is less reliable (timings vary by browser and network speed):
$(document) .off('click', '#btnExportToExcel') .on('click', '#btnExportToExcel', function (e) { e.preventDefault(); indicatorStart('Loading'); window.location.href = '/Index/ExcelReport'; // Give the browser 300ms to render the loader before cleaning up setTimeout(() => { indicatorStop(); }, 300); });
Pros:
- Minimal code changes required
Cons:
- If the file downloads faster than the timeout, the loader will flash briefly
- If the file takes longer to start downloading, the loader disappears early
- No way to handle download failures
In AJAX scenarios, the request runs asynchronously: after calling indicatorStart(), the browser has time to render the loader while waiting for the server response. indicatorStop() only runs after the response comes back, so the loader stays visible for the full duration of the request. With window.location.href, there's no "wait for response" step—your code triggers the download and immediately cleans up the loader before anything can be rendered.
内容的提问来源于stack exchange,提问作者fingers10

