原生JS中DOM元素类切换异常问题求助
问题分析与修复方案
问题根源
- 重复绑定
transitionend事件:每次执行隐藏逻辑时,都会给第一个sidebar-element绑定新的transitionend回调,旧的回调不会自动销毁。多次点击后,该元素会存在多个transitionend监听,导致sidebar的active类被多次移除,后续点击时误判状态重复添加。 - 无状态锁导致动画冲突:动画未完成时点击按钮,之前的定时器回调仍会执行,元素的
active类状态混乱,进一步导致sidebar的active类判断出错。
修复后的代码
JavaScript 部分
const SIDEBAR_DELAY_TIME = 150; let isAnimating = false; // 状态锁,防止动画期间重复点击 function handleSidebar() { const btn = document.querySelector('button'); const sidebar = document.querySelector('.sidebar'); const contents = document.querySelectorAll('.sidebar-element'); if (!btn || !sidebar) return; // 封装移除sidebar active类的函数,避免重复绑定 function removeSidebarActive() { sidebar.classList.remove('active'); contents[0].removeEventListener('transitionend', removeSidebarActive); } btn.addEventListener('click', () => { if (isAnimating) return; // 动画期间禁止点击 isAnimating = true; if (!sidebar.classList.contains('active')) { sidebar.classList.add('active'); contents.forEach((item, i) => { setTimeout(() => { item.classList.add('active'); // 最后一个元素动画完成后解锁 if (i === contents.length - 1) { isAnimating = false; } }, i * SIDEBAR_DELAY_TIME); }); } else { const keys = Array.from({ length: contents.length }, (_, i) => i).reverse(); let lastCallbackDone = false; // 标记最后一个隐藏回调是否执行 keys.forEach((key, i) => { setTimeout(() => { contents[key].classList.remove('active'); // 最后一个元素隐藏后,绑定transitionend事件移除sidebar的active if (key === 0 && !lastCallbackDone) { lastCallbackDone = true; contents[0].addEventListener('transitionend', removeSidebarActive, { once: true }); // 使用once确保只执行一次 } // 所有隐藏定时器执行完成后解锁 if (i === keys.length - 1) { setTimeout(() => { isAnimating = false; }, SIDEBAR_DELAY_TIME); } }, i * SIDEBAR_DELAY_TIME); }); } }); } handleSidebar();
关键修改点
- 添加
isAnimating状态锁,动画未完成时禁止触发新的点击逻辑,避免状态混乱。 - 将移除
sidebar的active类逻辑封装为独立函数removeSidebarActive,并在执行后移除事件监听,同时使用{ once: true }确保事件只触发一次,彻底解决重复绑定问题。 - 在显示/隐藏动画的最后一步解锁状态锁,确保只有动画完全结束后才能再次操作。
验证效果
修改后,无论点击多少次按钮,sidebar的active类都只会在正确的时机添加/移除,不会出现重复添加的情况,同时动画期间点击按钮也不会导致状态错乱。
内容的提问来源于stack exchange,提问作者HJ.
相关产品推荐
相关产品推荐

