有没有办法让事件监听器一次只处理一个事件?

前端开发 2026-07-08

我在尝试制作一个进度条,当按钮被点击时它会先填满再重置。我想确保不会因为过早点击按钮而把它搞乱。到目前为止,我的实现如下:

const button = document.getElementById("button");
const bar = document.getElementById("progressBar");

button.addEventListener("click", function (event) {
    bar.style.width = "100%";
    bar.style.opacity = "0%";
    setTimeout(() => {
        bar.style.transition = "none";
        bar.style.width = "0%";
        bar.style.opacity = "100%";
        bar.style.transition = "";
    }, 2200);
});
.progress-container {
  width: 100%;
  max-width: 500px;
  height: 12px;
  background-color: #e0e0e0;
  border-radius: 10px;
  overflow: hidden;
  margin: 8px auto;
  position: relative;
}

.progress-bar {
  width: 0%;
  height: 100%;
  background-color: #0071c5;
  background-image: linear-gradient(90deg, #0071c5, #00aeef);
  border-radius: 10px;
  opacity: 100%;
  transition: width 1s linear, opacity 1s linear 1s;
}
<button id="button">button
    <div class="progress-container">
        <div id="progressBar" class="progress-bar"></div>
    </div>
</button>

解决方案

使用类和过渡事件

与其使用 setTimeout 来同步你的过渡,不如改用 transitionstarttransitionend 事件?这样的好处是当你仅在CSS内改变过渡持续时间时,所有内容都会自动保持同步。使用 setTimeout 的情况你需要在使用不同的持续时间时同时修改CSS与 JS。

正如其他人已经指出的,你不应该在 <button> 里面放置一个 <div> 容器。我在下面的示例代码中已经为你修正了这一点。

与其在JS里手动添加/移除某些 .style 属性,不如使用一个新的CSS类来应用并在设置时启动过渡。这的好处是你可以把所有相关的样式相关代码都放在CSS里(更好的职责分离)。除此之外,这也让代码变得更加简洁。

防止用户搞乱过渡状态/避免点两次的部分,是通过按钮的 disabled 属性来实现的。它在过渡开始时被添加(在 click 处理函数 内),并在过渡结束时被移除。

const button = document.getElementById("button");
const bar = document.getElementById("progressBar");

bar.addEventListener('transitionend', () => {
  button.disabled = null;
  bar.classList.remove('transition-progress');
});

button.addEventListener("click", function (event) {
  button.disabled = true;
  bar.classList.add('transition-progress');
});
.progress-container {
  display: block;
  width: 100%;
  max-width: 500px;
  height: 12px;
  background-color: #e0e0e0;
  border-radius: 10px;
  overflow: hidden;
  margin: 8px auto;
  position: relative;
}

.progress-bar {
  display: block;
  width: 0%;
  height: 100%;
  background-color: #0071c5;
  background-image: linear-gradient(90deg, #0071c5, #00aeef);
  border-radius: 10px;
  opacity: 0%;
}

/* New class that triggers the transition */
.progress-bar.transition-progress {
  transition: width 1s linear, opacity 1s linear;
  width: 100%;
  opacity: 100%;
}
<button id="button">button
    <span class="progress-container">
        <span id="progressBar" class="progress-bar"></span>
    </span>
</button>

我注意到你最初有两条过渡在运行:一个针对 width,另一个在1s延迟后针对 opacity。因为我不确定你是否想要

  • 同时将 .progress-bar 元素“融合并放大” 在同一时间
  • 在增长阶段让 .progress-bar 增长并在增长后再进行淡出 *

下面是实现后者的版本(上面的代码实现前者的行为)。请注意,我所需要做的只是检查 哪个 属性触发了 transitionend 事件,并反转 .progress-bars opacity 值。

const button = document.getElementById("button");
const bar = document.getElementById("progressBar");

bar.addEventListener('transitionend', (event) => {
  if (event.propertyName === 'opacity') {
    button.disabled = null;
    bar.classList.remove('transition-progress');
  }
});

button.addEventListener("click", function (event) {
  button.disabled = true;
  bar.classList.add('transition-progress');
});
.progress-container {
  display: block;
  width: 100%;
  max-width: 500px;
  height: 12px;
  background-color: #e0e0e0;
  border-radius: 10px;
  overflow: hidden;
  margin: 8px auto;
  position: relative;
}

.progress-bar {
  display: block;
  width: 0%;
  height: 100%;
  background-color: #0071c5;
  background-image: linear-gradient(90deg, #0071c5, #00aeef);
  border-radius: 10px;
  opacity: 100%;
}

/* New class that triggers the transition */
.progress-bar.transition-progress {
  transition: width 1s linear, opacity 1s linear 1s;
  width: 100%;
  opacity: 0%;
}
<button id="button">button
    <span class="progress-container">
        <span id="progressBar" class="progress-bar"></span>
    </span>
</button>

进一步改进

与其直接把上面创建的类添加到 .progress-bar 元素,不如把它添加到 <button>?这会让你的HTML更短,同时也会让你的JS更简洁。你需要做的只是把CSS里的 .progress-bar.transition-progress 选择器改成 .transition-progress .progress-bar

const button = document.getElementById("button");
button.addEventListener('transitionend', () => {
  button.disabled = null;
  button.classList.remove('transition-progress');
});

button.addEventListener("click", function (event) {
  button.disabled = true;
  button.classList.add('transition-progress');
});
.progress-container {
  display: block;
  width: 100%;
  max-width: 500px;
  height: 12px;
  background-color: #e0e0e0;
  border-radius: 10px;
  overflow: hidden;
  margin: 8px auto;
  position: relative;
}

.progress-bar {
  display: block;
  width: 0%;
  height: 100%;
  background-color: #0071c5;
  background-image: linear-gradient(90deg, #0071c5, #00aeef);
  border-radius: 10px;
  opacity: 0%;
}

/* New class that triggers the transition, not the changed ordering */
.transition-progress .progress-bar {
  transition: width 1s linear, opacity 1s linear;
  width: 100%;
  opacity: 100%;
}
<button id="button">button
    <span class="progress-container">
        <span class="progress-bar"></span><!-- removed id -->
    </span>
</button>

这是可行的,因为 transitionend 事件会在DOM树中冒泡。是的,它也可以用在其他行为上:

const button = document.getElementById("button");
button.addEventListener('transitionend', (event) => {
  if (event.propertyName === 'opacity') {
    button.disabled = null;
    button.classList.remove('transition-progress');
  }
});

button.addEventListener("click", function (event) {
  button.disabled = true;
  button.classList.add('transition-progress');
});
.progress-container {
  display: block;
  width: 100%;
  max-width: 500px;
  height: 12px;
  background-color: #e0e0e0;
  border-radius: 10px;
  overflow: hidden;
  margin: 8px auto;
  position: relative;
}

.progress-bar {
  display: block;
  width: 0%;
  height: 100%;
  background-color: #0071c5;
  background-image: linear-gradient(90deg, #0071c5, #00aeef);
  border-radius: 10px;
  opacity: 100%;
}

/* New class that triggers the transition, not the changed ordering */
.transition-progress .progress-bar {
  transition: width 1s linear, opacity 1s linear 1s;
  width: 100%;
  opacity: 0%;
}
<button id="button">button
    <span class="progress-container">
        <span class="progress-bar"></span><!-- removed id -->
    </span>
</button>
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章