前端文件下载:从原理到实践

288 阅读3分钟

前言

在前端开发中,文件下载是一个常见的需求。本文将详细介绍前端文件下载的几种实现方式,包括使用原生HTML、Fetch API、Axios等不同方案,并分析它们的优缺点。

一、文件下载的基本原理

在浏览器中,文件下载本质上是通过HTTP请求获取文件数据,然后通过特定的方式触发浏览器的下载行为。主要有以下几种方式:

  1. 直接通过URL下载
  2. 通过Blob对象下载
  3. 通过Base64下载
  4. 通过流式下载

二、实现方式

1. 使用HTML的a标签下载

这是最简单的方式,适用于已知文件URL的情况:

<a href="文件URL" download="文件名">下载文件</a>
// 动态创建下载链接
function downloadByLink(url: string, filename: string) {
  const link = document.createElement('a');
  link.href = url;
  link.download = filename;
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
}

2. 使用Fetch API下载

async function downloadByFetch(url: string, filename: string) {
  try {
    const response = await fetch(url);
    const blob = await response.blob();
    const objectUrl = URL.createObjectURL(blob);
    
    const link = document.createElement('a');
    link.href = objectUrl;
    link.download = filename;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    
    // 释放URL对象
    URL.revokeObjectURL(objectUrl);
  } catch (error) {
    console.error('下载失败:', error);
  }
}

3. 使用Axios下载

import axios from 'axios';

async function downloadByAxios(url: string, filename: string) {
  try {
    const response = await axios({
      url,
      method: 'GET',
      responseType: 'blob'
    });
    
    const blob = new Blob([response.data]);
    const objectUrl = URL.createObjectURL(blob);
    
    const link = document.createElement('a');
    link.href = objectUrl;
    link.download = filename;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    
    URL.revokeObjectURL(objectUrl);
  } catch (error) {
    console.error('下载失败:', error);
  }
}

4. 大文件分片下载

对于大文件,我们可以实现分片下载:

async function downloadLargeFile(url: string, filename: string, chunkSize = 1024 * 1024) {
  try {
    const response = await fetch(url);
    const contentLength = response.headers.get('Content-Length');
    const totalSize = parseInt(contentLength || '0', 10);
    let downloadedSize = 0;
    
    const reader = response.body?.getReader();
    const chunks: Uint8Array[] = [];
    
    while (true) {
      const { done, value } = await reader!.read();
      
      if (done) break;
      
      chunks.push(value);
      downloadedSize += value.length;
      
      // 更新下载进度
      const progress = (downloadedSize / totalSize) * 100;
      console.log(`下载进度: ${progress.toFixed(2)}%`);
    }
    
    const blob = new Blob(chunks);
    const objectUrl = URL.createObjectURL(blob);
    
    const link = document.createElement('a');
    link.href = objectUrl;
    link.download = filename;
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    
    URL.revokeObjectURL(objectUrl);
  } catch (error) {
    console.error('下载失败:', error);
  }
}

三、下载进度显示

我们可以通过监听XMLHttpRequest或Fetch API的进度事件来显示下载进度:

function downloadWithProgress(url: string, filename: string) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.responseType = 'blob';
    
    xhr.onprogress = (event) => {
      if (event.lengthComputable) {
        const progress = (event.loaded / event.total) * 100;
        console.log(`下载进度: ${progress.toFixed(2)}%`);
      }
    };
    
    xhr.onload = () => {
      if (xhr.status === 200) {
        const blob = xhr.response;
        const objectUrl = URL.createObjectURL(blob);
        
        const link = document.createElement('a');
        link.href = objectUrl;
        link.download = filename;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        
        URL.revokeObjectURL(objectUrl);
        resolve(true);
      } else {
        reject(new Error('下载失败'));
      }
    };
    
    xhr.onerror = () => {
      reject(new Error('下载失败'));
    };
    
    xhr.send();
  });
}

四、下载样式优化

我们可以为下载按钮添加一些样式,提升用户体验:

.download-button {
  display: inline-flex;
  align-items: center;
  padding: 8px 16px;
  background-color: #1890ff;
  color: white;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.3s;
}

.download-button:hover {
  background-color: #40a9ff;
}

.download-button:active {
  background-color: #096dd9;
}

.download-progress {
  width: 100%;
  height: 4px;
  background-color: #f0f0f0;
  border-radius: 2px;
  margin-top: 8px;
}

.download-progress-bar {
  height: 100%;
  background-color: #1890ff;
  border-radius: 2px;
  transition: width 0.3s;
}

五、注意事项

  1. 跨域问题:如果下载的文件在另一个域名下,需要确保服务器设置了正确的CORS头。
  2. 文件大小限制:浏览器对Blob对象的大小有限制,大文件下载需要考虑分片。
  3. 内存占用:下载大文件时要注意内存占用,及时释放不需要的资源。

每种方式都有其适用场景,开发者可以根据具体需求选择合适的方式。同时,在实际应用中还需要注意跨域、文件大小、内存占用等问题,确保下载功能的稳定性和用户体验。

希望本文能帮助大家更好地理解和实现前端文件下载功能。如果有任何问题或建议,欢迎在评论区留言讨论。