Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detect that given element has been removed from the DOM without sacrificing performance

I've got these:

const element = this.getElementById("victim")

function releaseKraken(targetElement) {}

I want the function to be called when element is removed from DOM.

I can imagine something like this:

element.onRemove(() => releaseKraken(element))

I understand that I need MutationObserver, but all the documentation I found focuses on watching given element's children, whereas I need to watch the element itself.

UPD: the question How to detect element being added/removed from dom element? focuses on watching children of a given parent. I don't want to watch children or parent. I want to be notified when given element is removed from the DOM. Not it's children. And I don't want to set up a watcher on given element's parent (unless that's the only option) because it'll be a performance impact.

UPD2: if I set up a MutationObserver on document, this will result in the callback being triggered thousands or even millions of times per session, and each time the callback will have to filter a huge list of removed elements to see if it contains the one in question. That's just crazy.

I need something simple like I showed above. I want the callback to be triggered exactly once: when the given element is removed.

like image 812
Andrey Mikhaylov - lolmaus Avatar asked May 17 '18 12:05

Andrey Mikhaylov - lolmaus


People also ask

How to detect whether an element has been removed from Dom?

We can detect whether an element has been removed DOM using the MutationObserver object. MutationObserver provides the ability to observe for changes being made to the DOM tree. Consider the below markup where there is an element #parent containing #child.

How to check if node has been removed from Dom?

We can loop over the nodes in removedNodes to find out whether the removed node matches our given criteria (in this case we are looking if element with ID child is present). Once we find that our intended element has been removed from DOM, we can stop observing mutations using disconnect () method.

How to observe changes being made to the DOM tree?

MutationObserver provides the ability to observe for changes being made to the DOM tree. Consider the below markup where there is an element #parent containing #child. We would like to know when #child is removed from the DOM.

How to stop observing mutations when element is removed from Dom?

Once we find that our intended element has been removed from DOM, we can stop observing mutations using disconnect () method. Once #child has been removed, it will be detected.


Video Answer


3 Answers

As you said, MutationObserver only allows you to detect when the children of an element are manipulated. That means you'll need to listen to the parent and check what changes were made to see if the target element was removed.

function onRemove(element, callback) {
  const parent = element.parentNode;
  if (!parent) throw new Error("The node must already be attached");

  const obs = new MutationObserver(mutations => {
    for (const mutation of mutations) {
      for (const el of mutation.removedNodes) {
        if (el === element) {
          obs.disconnect();
          callback();
        }
      }
    }
  });
  obs.observe(parent, {
    childList: true,
  });
}

then with your example instead of

element.onRemove(() => releaseKraken(element));

you can do

onRemove(element, () => releaseKraken(element));

This approach should be plenty fast if all you are doing is watching a single element. While it may seem like a decent amount of looping, it is pretty rare for removedNodes to be more than one node, and unless something is removing tons of siblings all at once, mutations is going to be quite small too.

You could also consider doing

callback(el);

which would allow you to do

onRemove(element, releaseKraken);
like image 105
loganfsmyth Avatar answered Oct 08 '22 23:10

loganfsmyth


An alternative, shorter version of loganfsmyth's excellent solution:

function onRemove(el, callback) {
  new MutationObserver((mutations, observer) => {
    if(!document.body.contains(el)) {
      observer.disconnect();
      callback();
    }
  }).observe(document.body, { childList: true });
}

Usage is the same:

onRemove(myElement, function() {
  console.log("The element was removed!");
})
like image 25
2 revs Avatar answered Oct 08 '22 22:10

2 revs


Here is a solution I found on this page

document.getElementById("delete_one_div").addEventListener('click', function() {
  var divToDelete = document.getElementsByTagName("div")[0];
  divToDelete.parentNode.removeChild(divToDelete);
});

var element = document.getElementById("div_to_be_watched")
var in_dom = document.body.contains(element);
var observer = new MutationObserver(function(mutations) {
  if (in_dom && !document.body.contains(element)) {
    console.log("I was just removed");
    in_dom = false;
    observer.disconnect();
  }

});
observer.observe(document.body, { childList: true });
<div id="test">Test</div>
<div id="div_to_be_watched">Div to be watched</div>
<div class="div_to_be_watched">Second test</div>
<button id="delete_one_div">Delete one div</button>

EDIT

I edited snippet a little bit. You have two options:

  1. Use it the way it is. And it is not very memory consuming since the if condition is not really complicated (only to check whether the body contains an element) and it observes only to the moment of remove and then it stops,
  2. Make observer observe only specific element to limit the event triggers.
like image 30
Krzysztof Janiszewski Avatar answered Oct 08 '22 21:10

Krzysztof Janiszewski