有没有办法让事件监听器一次只处理一个事件?
我在尝试制作一个进度条,当按钮被点击时它会先填满再重置。我想确保不会因为过早点击按钮而把它搞乱。到目前为止,我的实现如下:
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 来同步你的过渡,不如改用 transitionstart 与 transitionend 事件?这样的好处是当你仅在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>