You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多按钮下载进度条适配问题:点击获取元素ID及对应文件

适配多按钮的下载进度条实现方案

要实现多按钮独立下载并对应各自进度条,核心是让每个下载按钮和其对应的进度条建立关联,通过事件委托处理所有按钮的点击,同时利用自定义属性存储每个按钮的专属下载路径。


1. HTML结构调整

每个下载项(按钮+进度条)放在独立容器中,给按钮统一类名,用data-file存储下载路径,确保按钮和进度条一一对应:

<html class="no-js" lang="">
<head>
  <script src="js/app.js" defer></script>
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
  <!-- 第一个下载项 -->
  <div class="download-item">
    <button class="download-start-button" data-file="img/jdk-22_windows-x64_bin.exe">下载JDK</button>
    <div class="download-progress-bar__container">
      <div class="download-progress-bar__progress"></div>
    </div>
  </div>

  <!-- 第二个下载项 -->
  <div class="download-item">
    <button class="download-start-button" data-file="img/idea-2024.1.exe">下载IDEA</button>
    <div class="download-progress-bar__container">
      <div class="download-progress-bar__progress"></div>
    </div>
  </div>
</body>
</html>

2. CSS样式适配

去掉单个ID的专属样式,改用类选择器适配多个下载项,同时优化进度条视觉效果:

html {
  height: 100%;
}
body {
  height: 100%;
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 20px;
  margin: 0;
}

.download-start-button {
  background: #c50408;
  color: honeydew;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.download-start-button:disabled {
  background-color: rgb(39, 92, 74);
  cursor: not-allowed;
}

.download-item {
  display: flex;
  flex-flow: nowrap column;
  align-items: center;
  gap: 10px;
}

.download-progress-bar__container {
  width: 400px;
  height: 40px;
  border-radius: 10px;
  background: #eee;
}

.download-progress-bar__progress {
  background: linear-gradient(to right, rgba(255, 0, 0, 0), rgb(255, 0, 0));
  border-radius: 10px;
  color: honeydew;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 0;
  height: 100%;
  transition: width 0.2s ease;
}

.download-progress-bar__progress.error {
  background: linear-gradient(to right, rgba(138, 0, 226, 0), rgb(138, 0, 226));
}

3. JavaScript核心逻辑修改

使用事件委托监听所有下载按钮的点击,在处理函数中获取当前按钮的信息、对应进度条,执行独立的下载逻辑:

console.log('Initially ' + (window.navigator.onLine ? 'on' : 'off') + 'line');

window.addEventListener('online', () => console.log('Became online'));
window.addEventListener('offline', () => console.log('Became offline'));

// 事件委托:统一处理所有下载按钮的点击
document.body.addEventListener('click', async (e) => {
  // 只处理下载按钮的点击
  if (!e.target.matches('.download-start-button')) return;

  const button = e.target;
  const fileToDownload = button.dataset.file;
  // 找到当前按钮对应的进度条(父容器内的进度条元素)
  const progressElem = button.closest('.download-item').querySelector('.download-progress-bar__progress');

  // 避免重复点击
  if (button.disabled) return;

  // 初始化状态
  button.disabled = true;
  progressElem.classList.remove('error');
  progressElem.textContent = '';
  progressElem.style.width = '0%';

  const dataChunks = [];
  try {
    const response = await fetch(`/${fileToDownload}`);
    if (!response.ok) throw new Error('网络请求失败');

    const reader = response.body.getReader();
    const totalSize = Number(response.headers.get('content-length'));
    let totalSizeDownloaded = 0;

    // 分段读取下载数据
    async function readData() {
      const result = await reader.read();
      if (result.value) {
        dataChunks.push(result.value);
        totalSizeDownloaded += result.value.length;
        const percentage = Math.floor((totalSizeDownloaded / totalSize) * 100);

        console.log(`${totalSizeDownloaded}/${totalSize} (${percentage}%)`);
        progressElem.textContent = `${percentage}%`;
        progressElem.style.width = `${percentage}%`;
      }

      if (!result.done) await readData();
    }

    await readData();

    // 生成下载链接并触发下载
    const downloadAnchor = document.createElement('a');
    const blob = new Blob(dataChunks);
    downloadAnchor.href = URL.createObjectURL(blob);
    // 只保留文件名作为下载文件名
    downloadAnchor.download = fileToDownload.split('/').pop();
    document.body.appendChild(downloadAnchor);
    downloadAnchor.click();
    document.body.removeChild(downloadAnchor);
    // 释放Blob URL,避免内存泄漏
    URL.revokeObjectURL(downloadAnchor.href);
    console.log('下载完成');
  } catch (err) {
    console.error('下载错误:', err);
    progressElem.textContent = '下载失败';
    progressElem.classList.add('error');
  } finally {
    // 恢复按钮状态
    button.disabled = false;
  }
});

关键改动说明

  • 事件委托:通过父元素统一监听点击,无需给每个按钮单独绑定事件,支持动态添加按钮的场景。
  • 元素关联:用closest('.download-item')定位当前按钮所在容器,确保每个按钮对应自己的进度条。
  • 自定义属性:data-file存储每个按钮的下载路径,避免硬编码,扩展性更强。
  • 内存优化:下载完成后调用URL.revokeObjectURL释放Blob资源,防止内存泄漏。
  • 异步简化:改用async/await替代链式Promise,代码更易读维护。

内容的提问来源于stack exchange,提问作者AetherxMana

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 00:24:51