3

I have the following code that works, but if I click a panel and then click it again, the mutation observer fires twice on the second click. Does anyone know the cause of this and how to stop it?

I'm guessing it's because it fires once for the add class and once for the remove class. but if that's the case, the classList.contains shouldn't be true when you do the remove class (as can be seen if you click on a different panel - only that one add class event fires)

const activeClass = 'active'; const $panels = $('.panel'); let counter = 0; $panels.on('click', e => { const $panel = $(e.currentTarget); $panels.removeClass(activeClass); $panel.addClass(activeClass); }); $panels.each((index, panel) => { const observer = new MutationObserver(mutationsList => { for (let i = 0; i < mutationsList.length; i++) { if (mutationsList[i].attributeName === 'class' && mutationsList[i].target.classList.contains(activeClass)) { console.log(mutationsList[i].target.className, counter); counter++; } } }); observer.observe(panel, { attributes: true, childList: false, subtree: false }); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <div class="panel panel--1"> panel </div> <div class="panel panel--2"> panel </div> <div class="panel panel--3"> panel </div>

3 Answers 3

1

Your code makes two mutations when there's an active panel regardless of which panel we click:

  • Record 1: removing the class
  • Record 2: adding the class

The difference in behavior you observe for the same-element click is caused by your handler's if condition. It may be surprising but it sees the active class is present on the element in the first mutation record (for removing) because MutationObserver's handler runs as a micro-task - meaning it runs after the current task completes - meaning that addClass() has already added the class on the same element.

As a solution, you can skip the work if the clicked element is already active:

$panels.on('click', e => { const $panel = $(e.currentTarget); if (!$panel.hasClass(activeClass)) { $panels.removeClass(activeClass); $panel.addClass(activeClass); } }); 
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for the answer, unfortunately the removing the same class was just me taking a short cut in my replicating the code - the actual class that is removed is different (and therefore would create two mutation records when the addClass happened)
1

After a lot of debugging, I have found that for each action on the class list, a new mutation record is added to the mutation list. However, the target of the mutation record is the final state of the element, and not the state of the element when the event occurred.

To get around this, I have had do a test on the target's class name to see if it has already had a mutation fired (luckily my elements have unique classes) and if not fire the function.

This probably isn't the best was of doing things but seems to solve my problem so if anyone has a better way then please feel free to post it

const activeClass = 'active'; const $panels = $('.panel'); $panels.on('click', e => { const $panel = $(e.currentTarget); $panel.removeClass(activeClass); $panel.addClass(activeClass); }); $panels.each((index, panel) => { const observer = new MutationObserver(mutationsList => { const groupedMutations = []; for (let i = 0; i < mutationsList.length; i++) { if (mutationsList[i].attributeName === 'class' && groupedMutations.indexOf(mutationsList[i].target.className) == -1 && mutationsList[i].target.classList.contains(activeClass)) { groupedMutations.push(mutationsList[i].target.className); console.log(mutationsList[i].target.className, i) } } }); observer.observe(panel, { attributes: true, childList: false, subtree: false }); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <div class="panel panel--1"> panel </div> <div class="panel panel--2"> panel </div> <div class="panel panel--3"> panel </div>

3 Comments

What is your ultimate aim in adding the mutation observer? Is it just for logging, or do you aim to do something there. If it is to log which panel's class was modified, then your curent code seems fine. I would keep the modified divs in classModifiedNodes array, but that would be up to you.
I need to see when the div becomes active and when it does, I process the data in it to send off to google analytics - the panels contain different forms and passing between each is step is done using some ajax that I am unable to modify as I don't have access to the source js
Then this is the best way to do it
0

Mutation Observer is an asynchronous. This means that it keeps a record of all the changes, and calls the callback function once passing the full record of changes done to the DOM.

After clicking once when you click again

Record

  1. class removed
  2. class added

Current status: Class added (when callback is called)

So, classlist contains returns true.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.