javascriptdom-eventsuserscriptsmutation-observers

How do I change the innerHTML of a newly created element watched by a Mutual Observer, WITHOUT causing memory leaks and freezes?


Imagine a website that has infinite scrolling, where a link saying download, appears underneath every post. I would like to change from a word to something else of anything else. (In my case a 24x24 icon image rather than the word "download" but don't worry about that for now).

HTML

<div id="container">
  <span class="download"> Initial download link</span>
</div>

CSS

body { 
  background    : #1c1c1c;
  }
span { 
  background    : darkcyan; 
  color         : #dcdcdc; 
  padding       : 8px; 
  font-family   : Tahoma; 
/*font-weight   : bold; */
  display       : inline-block;
  margin-right  : 8px; 
  margin-bottom : 8px;
  border        : 2px ridge #1c1c1c;
  }
.download {
  background-color : #cc3c3c;
  border           : 3px ridge #3c3c3c;
  }

Javascript

let i = 1

function insertElement() {
  var newSpan = document.createElement("span")
  newSpan.classList.add("download")
  var newText = document.createTextNode("new button #" + i)
  newSpan.appendChild(newText)
  //console.log(newSpan.classList)

  if (i < 210) {
    document.getElementById("container").appendChild(newSpan)
  }
  setTimeout(insertElement, 4200)
  i++
}

//(function(){
// do some stuff
setTimeout(insertElement)
//})();

var elemToObserve = document.getElementById("container")

//var observer = new MutationObserver(function (mutations) {
var observer = new MutationObserver((mutations) => {
  for (let mutation of mutations) {
    if (mutation.type === "childList") {
      //do NOT use console.log for mutation.target as it causes a HUGE INSTANTANEOUS memory leak
      //console.log(mutation)
      //if (mutation.nodeType === Node.ELEMENT_NODE ) {
      if (mutation.target.classList.contains("download") !== true) {
        console.log("a download button was found")
      }
    }
  }
})

observer.observe(elemToObserve, {
  childList: true,
  attributes: true,
  characterData: false,
  subtree: true,
})

Once this new element with a class of .download has been tracked using the MutationObserver, I would like to change the innerText or innerHTML of it.

You can see what's going on, can't you? When I use innerHTML or innerText to change the value of a newly tracked element from MutualObserver, it causes some recursion, to monitor a change of a change of a change.

Here are some ideas for what to change the textNode to. \/ or a base64 encoded image.

You can test out my work to try for yourself on jsfiddle.

To whoever closed my question

My question is very different than the one earlier suggested, due to my use case making use of infinite scroll or infinite pagination on click.

The accepted answer (and only answer) in the linked question, gives a lot of tips and tricks for single page apps (SPAs) which are resource intensive and DOM heavy, along with the penultimate suggestion to simply close or terminate the MutationObserver, using observer.disconnect which cannot be done because I wish to change multiple links at once, not just one, especially as the user could simply click "show more results" to show more entries on the page.

Here's a more complicated example to illustrate.

Upon looking at the more complicated example above, what would happen if I was to use the observe.connect feature for MutualObserver, would be that it would count every pre-existing element again, a second time. As MutualObserver is very resource intensive on CPU and disk usage (especially on smartphones and people multitasking on laptops or using external sound cards), I can't really count things twice, for speed and performance issues.


Solution

  • avoid subtree unless you truly need and try monitoring addedNodes not the whole DOM. ex:

    for (const mutation of mutations) {
     for (const node of mutation.addedNodes) {
       ...
     }
    }