使用 a 标签下载
-
这是最简单直接的下载方式,适用于已知文件 URL 的情况;
-
优点:
- 简单易用;
- 兼容性好 (包括旧版浏览器);
-
缺点:
- 无法处理需要授权的情况 (如需要携带 cookie 或 token);
- 无法处理跨域资源 (除非服务器设置 CORS);
-
示例代码:
<!DOCTYPE html> <html> <head> <title>a标签下载示例</title> </head> <body> <a href="https://example.com/files/sample.pdf" download="sample.pdf">下载PDF文件</a> <!-- 动态创建a标签下载 --> <button onclick="downloadFile()">动态下载</button> <script> function downloadFile() { const link = document.createElement('a'); link.href = 'https://example.com/files/sample.pdf'; link.download = 'sample.pdf'; document.body.appendChild(link); link.click(); document.body.removeChild(link); } </script> </body> </html>
使用 window.open 或 location.href
-
这种方式类似于 a 标签,但更适用于通过 JavaScript 触发下载;
-
优点:
- 代码极其简单;
-
缺点:
- 会打开新窗口;
- 无法自定义下载文件名;
- 同源策略限制;
- 可能被浏览器拦截;
-
示例代码:
window.open('https://example.com/files/sample.pdf', '_blank'); // 或 window.location.href = 'https://example.com/files/sample.pdf';
使用 Fetch API + Blob 下载
-
这种方法适用于需要添加认证头或处理 API 返回的文件数据;
-
优点:
- 可以设置请求头 (如认证信息);
- 可以处理各种 HTTP 方法 (GET/POST等);
- 可以在下载前对数据进行处理;
-
缺点:
- 代码相对复杂;
- 对于大文件可能会占用较多内存;
- IE 不支持 (但可以使用 polyfill);
-
示例代码:
<button onclick="downloadFile()">下载文件</button> <script> function downloadFile() { fetch('https://example.com/sample.pdf') .then(response => { if (!response.ok) { throw new Error('网络响应不正常'); } return response.blob(); }) .then(blob => { const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'sample.pdf'; document.body.appendChild(a); a.click(); // 清理 window.URL.revokeObjectURL(url); document.body.removeChild(a); }) .catch(error => { console.error('下载失败:', error); }); } </script>
使用 XMLHttpRequest + Blob 下载
-
这是传统的 AJAX 方式,与 Fetch 类似但使用回调而非 Promise;
-
优点:
- 兼容性比 Fetch 更好 (支持更旧的浏览器);
- 可以设置请求头;
-
缺点:
- 基于回调的 API 不如 Promise 友好;
- 代码相对冗长;
-
示例代码:
<!DOCTYPE html> <html> <head> <title>XHR下载示例</title> </head> <body> <button onclick="downloadWithXHR()">使用XHR下载</button> <script> function downloadWithXHR() { const xhr = new XMLHttpRequest(); xhr.open('GET', 'https://example.com/api/download', true); xhr.setRequestHeader('Authorization', 'Bearer your-token-here'); xhr.responseType = 'blob'; xhr.onload = function() { if (this.status === 200) { const blob = this.response; const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'xhr-download.pdf'; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); } else { console.error('下载失败'); } }; xhr.onerror = function() { console.error('请求错误'); }; xhr.send(); } </script> </body> </html>
下载 Base64 数据或文本
-
适用于需要下载非二进制数据 (如文本、图片 Base64 等);
-
优点:
- 可以直接下载生成的内容;
- 不需要服务器支持;
-
缺点:
- 不适合大文件;
- Base64 编码会增加约 33% 的体积;
-
示例代码:
<!DOCTYPE html> <html> <head> <title>Base64下载示例</title> </head> <body> <button onclick="downloadBase64()">下载Base64图片</button> <button onclick="downloadText()">下载文本文件</button> <script> function downloadBase64() { const base64Data = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA...'; const link = document.createElement('a'); link.href = base64Data; link.download = 'image.png'; link.click(); } function downloadText() { const content = '这是一段要下载的文本内容'; const blob = new Blob([content], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'example.txt'; a.click(); URL.revokeObjectURL(url); } </script> </body> </html>
下载前端生成的内容
-
适用于 PWA 应用或需要离线访问的场景;
-
特点:
- 适合在前端生成内容后下载;
- 可以下载各种类型的数据 (文本、JSON、CSV 等);
- 不需要服务器参与;
-
示例代码:
<button onclick="downloadText()">下载文本文件</button> <button onclick="downloadJSON()">下载JSON文件</button> <script> function downloadText() { const content = '这是一段示例文本\n第二行内容'; const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'example.txt'; a.click(); URL.revokeObjectURL(url); } function downloadJSON() { const data = { name: '张三', age: 30, hobbies: ['阅读', '游泳', '编程'] }; const content = JSON.stringify(data, null, 2); const blob = new Blob([content], { type: 'application/json;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'data.json'; a.click(); URL.revokeObjectURL(url); } </script>
使用 Web Workers 进行后台下载
-
对于非常大的文件或需要复杂处理的下载,可以使用 Web Workers 在后台线程中进行,避免阻塞 UI 线程;
-
优点:
- 不阻塞 UI 线程;
- 可以报告下载进度;
- 适合大文件下载;
-
缺点:
- 实现复杂;
- 需要额外的 worker 文件;
-
示例代码:
// worker.js self.onmessage = function(e) { const { url, filename } = e.data; fetch(url) .then(response => response.blob()) .then(blob => { self.postMessage({ status: 'completed', blob, filename }); }) .catch(error => { self.postMessage({ status: 'error', error: error.message }); }); }; // 主线程 const worker = new Worker('worker.js'); worker.onmessage = function(e) { if (e.data.status === 'completed') { const url = URL.createObjectURL(e.data.blob); const a = document.createElement('a'); a.href = url; a.download = e.data.filename; a.click(); URL.revokeObjectURL(url); } else { console.error('下载失败:', e.data.error); } }; worker.postMessage({ url: 'https://example.com/large-file.zip', filename: 'large-file.zip' });
分片并发下载文件
-
下面是一个实现分片并发下载文件并拼装的前端代码方案;这个方案假设后端支持分片下载,并且可以通过请求头或 URL 参数指定分片信息;
-
实现思路:
- 首先获取文件总大小和分片信息;
- 根据分片大小计算需要分成多少片;
- 并发请求所有分片;
- 按照分片顺序拼装数据;
- 触发浏览器下载完整文件;
-
关键点说明:
- HEAD 请求:首先发送 HEAD 请求获取文件大小,确定需要分成多少片;
- Range 请求:使用 HTTP Range 头请求 特定字节范围的数据;
- 并发控制:使用 concurrentMap 函数控制并发请求数量,避免浏览器限制;
- 数据拼装:按照分片索引顺序将 ArrayBuffer 数据合并;
- 下载触发:使用 Blob 和 Object URL 创建下载链接并触发点击;
-
优化建议:
- 断点续传:可以本地存储已下载的分片信息,实现断点续传;
- 进度显示:添加进度回调函数,显示下载进度;
- 错误重试:为每个分片添加重试机制;
- 内存优化:对于超大文件,可以考虑流式处理而不是全部加载到内存;
-
注意事项:
- 后端必须支持 Range 请求;
- 跨域请求需要正确配置 CORS;
- 对于超大文件,内存消耗可能较大,可以考虑使用 Streams API 处理;
-
示例代码:
/** * 分片并发下载文件 * @param {string} url 文件下载地址 * @param {object} options 配置选项 * @param {number} [options.chunkSize=1*1024*1024] 分片大小,默认1MB * @param {number} [options.maxConcurrent=5] 最大并发数 * @param {object} [options.headers={}] 自定义请求头 * @param {string} [options.filename] 下载文件名,如果不提供则尝试从响应头获取 */ async function downloadFileWithChunks(url, options = {}) { const { chunkSize = 1 * 1024 * 1024, // 默认分片大小1MB maxConcurrent = 5, // 最大并发数 headers = {}, filename } = options; // 1. 首先发送HEAD请求获取文件总大小 const headResponse = await fetch(url, { method: 'HEAD', headers }); if (!headResponse.ok) { throw new Error(`Failed to get file info: ${headResponse.status} ${headResponse.statusText}`); } const contentLength = headResponse.headers.get('Content-Length'); if (!contentLength) { throw new Error('Content-Length header is missing'); } const totalSize = parseInt(contentLength, 10); const chunkCount = Math.ceil(totalSize / chunkSize); console.log(`File size: ${totalSize} bytes, will be downloaded in ${chunkCount} chunks`); // 2. 创建所有分片的请求 const chunkPromises = []; const chunkRanges = []; for (let i = 0; i < chunkCount; i++) { const start = i * chunkSize; const end = Math.min(start + chunkSize - 1, totalSize - 1); chunkRanges.push({ start, end, index: i }); } // 3. 使用并发控制下载所有分片 const chunks = await concurrentMap( chunkRanges, async ({ start, end, index }) => { console.log(`Downloading chunk ${index + 1}/${chunkCount} [${start}-${end}]`); const response = await fetch(url, { headers: { ...headers, 'Range': `bytes=${start}-${end}` } }); if (!response.ok) { throw new Error(`Failed to download chunk ${index}: ${response.status} ${response.statusText}`); } return { index, data: await response.arrayBuffer() }; }, maxConcurrent ); // 4. 按照分片顺序拼装数据 console.log('All chunks downloaded, assembling...'); // 按索引排序 chunks.sort((a, b) => a.index - b.index); // 计算总大小并创建缓冲区 const totalBytes = chunks.reduce((sum, chunk) => sum + chunk.data.byteLength, 0); const buffer = new Uint8Array(totalBytes); // 合并所有分片数据 let offset = 0; for (const chunk of chunks) { buffer.set(new Uint8Array(chunk.data), offset); offset += chunk.data.byteLength; } console.log('File assembled, creating download link'); // 5. 创建下载链接 const blob = new Blob([buffer]); const downloadUrl = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = downloadUrl; a.download = filename || getFilenameFromHeaders(headResponse.headers) || 'download'; document.body.appendChild(a); a.click(); // 清理 setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(downloadUrl); }, 100); } /** * 并发控制执行异步任务 * @param {Array} items 要处理的数组 * @param {Function} mapper 异步处理函数 * @param {number} concurrency 并发数 * @returns {Promise<Array>} 处理结果数组 */ async function concurrentMap(items, mapper, concurrency = 5) { const results = []; const activePromises = new Set(); for (const [index, item] of items.entries()) { // 等待有空闲的并发槽 while (activePromises.size >= concurrency) { await Promise.race(activePromises); } const promise = mapper(item, index).then(result => { activePromises.delete(promise); return result; }); activePromises.add(promise); results.push(promise); } return Promise.all(results); } /** * 从响应头中提取文件名 * @param {Headers} headers 响应头 * @returns {string|null} 文件名 */ function getFilenameFromHeaders(headers) { const contentDisposition = headers.get('Content-Disposition'); if (!contentDisposition) return null; const filenameMatch = contentDisposition.match(/filename\*?=["']?(?:UTF-\d["']*)?([^;"']*)["']?;?/i); if (filenameMatch && filenameMatch[1]) { return decodeURIComponent(filenameMatch[1]); } return null; } // 使用示例 downloadFileWithChunks('https://example.com/large-file.zip', { chunkSize: 2 * 1024 * 1024, // 2MB分片 maxConcurrent: 3, // 最大3个并发请求 filename: 'large-file.zip', headers: { 'Authorization': 'Bearer your-token' } }).then(() => { console.log('File downloaded successfully'); }).catch(error => { console.error('Download failed:', error); });
断点续传
css🌰 实现文字智能适配背景
上一篇