点击同一面板时Mutation observer两次触发的原因与解决办法问询
问题成因
两次触发本质是MutationObserver回调的执行时机、以及判断条件的逻辑共同导致的:
- 当你点击已经处于激活状态的面板时,点击逻辑会先执行
$panels.removeClass(activeClass),这个操作会把当前点击面板已有的active类移除,触发第一次该面板的class属性变更;紧接着执行$panel.addClass(activeClass),又给该面板加回active类,触发第二次class属性变更。 - Observer回调里的判断用的是
mutationsList[i].target.classList.contains(activeClass),这个判断取的是回调执行时元素的实时class状态,而两次mutation进入回调时,元素已经完成了「删active-加active」的全流程,class已经是带active的状态,所以两次判断都命中,就执行了两次逻辑。 - 点击未激活的面板时不会出现该问题的原因是:原激活面板删active的mutation触发时,该面板后续不会再加回active,回调判断时它的class已经没有active,不会命中;新激活面板只有加active的一次变更,只会命中一次,所以整体只触发一次逻辑。
解决方案
方案1:点击时先判断面板是否已激活,避免无意义的类操作
在点击逻辑开头加判断,如果当前点击的面板已经有active类,直接终止后续操作,从根源上避免多余的属性变更,改造成本最低,优先推荐:
$panels.on('click', e => { const $panel = $(e.currentTarget); // 新增判断:已激活则不执行后续操作 if ($panel.hasClass(activeClass)) return; $panels.removeClass(activeClass); $panel.addClass(activeClass); });
方案2:基于属性变更的前后值判断,避免实时状态误判
给Observer配置开启attributeOldValue,通过对比class变更的前后值,只有当变更前没有active、变更后有active时才触发逻辑,不受元素实时状态影响,适配性更强:
$panels.each((index, panel) => { const observer = new MutationObserver(mutationsList => { for (let i = 0; i < mutationsList.length; i++) { const mutation = mutationsList[i]; if (mutation.attributeName === 'class') { const oldClass = mutation.oldValue || ''; const newClass = mutation.target.className || ''; // 严谨判断active类的新增动作,避免类名前缀重合误判 const hasOldActive = oldClass.split(/\s+/).includes(activeClass); const hasNewActive = newClass.split(/\s+/).includes(activeClass); if (!hasOldActive && hasNewActive) { console.log(mutation.target.className, counter); counter++; } } } }); observer.observe(panel, { attributes: true, attributeOldValue: true, // 开启记录变更前的属性值 childList: false, subtree: false }); });
内容的提问来源于stack exchange,提问作者Pete
相关产品推荐
相关产品推荐

