1

I have a plain webpage that loads the majority of it's content through an AJAX call that returns a large amount of complex HTML. When I place the retrieved HTML in the DOM, it cripples the browser for quite a while (between 5 seconds on Chrome and 35 seconds on Edge).

Example how I append the HTML to the DOM:

$.ajax("example.php").done(function (response) {
    const contentElement = document.getElementById('results');
    contentElement.innerHTML = response;
});

I want to avoid having to return JSON and converting it to HTML at all cost because of the complexity of the application.

The odd thing is that the browser gets crippled a little while after the inserted HTML is already visible. See the timeline below, where I can see the HTML on my screen (with proper styling) before the ~5 sec long Parse HTML events happen.

Timeline performance

How do I speed up the parsing and appending of the HTML to the DOM?

Edit: I have tried multiple browsers and multiple methods of injecting the HTML (documentFragments, innerHTML, jquery .html(), append()). All methods are roughly as slow.

Edit2: The exact HTML injected can be seen in this gist: https://gist.github.com/Rhinni/3032e74bab0de8f40e08a3392c0243b1

Bhargav Rao
  • 50,140
  • 28
  • 121
  • 140
  • can you show an example of the html you are trying to parse? – floor Mar 27 '18 at 15:07
  • @floor I have added a gist of the html, thanks in advance – Sander Muller Mar 28 '18 at 14:13
  • This may help https://stackoverflow.com/questions/21544600/speeding-up-appending-html-elements-into-div-via-javascript – Jimenemex Mar 28 '18 at 14:30
  • Thanks @Jimenemex but the documentFragment is useful when you place multiple items (like from a loop), and collect it so you can place it in the DOM in one go. However, in my case I already have all the HTML together and place it in one piece. – Sander Muller Mar 28 '18 at 15:59

3 Answers3

4

Part 1 - It's not the way the code is loaded, it's just the code is invalid and wouldn't work even if hardcoded on the page.


"The odd thing is that the browser gets crippled a little while after the inserted HTML is already visible. See the timeline below, where I can see the HTML on my screen (with proper styling) before the ~5 sec long Parse HTML events happen."


There are some things that should be addressed concerning the practicality of the HTML (its preposterousness speaks for itself), its validity (which it isn't) and functionality, (which it isn't and probably never had any).

You should validate your HTML because it is very invalid, but before we get into that, when you decide to validate that mess, you'll need to separate it into about 16 parts because most online services will fall or cut the validation process early if given that much to process at one time.

The following is a list of issues that are not isolated problems due to a typo. These issues are repeated multiple times. What concerns me the most is that the values and the majority of the variables appear to be customized by hand. Hopefully I am mistaken and you didn't spend hours on customizing values that will hinder rather than be of any real use.


1. #IDs must be unique -- under no circumstances should there ever be a duplicated #ID on the same page.


14 #accordion- fixed, 14 #headingOne - fixed, 7 #model, 7 #type, 7#brand,...

There's more duped #IDs, I changed the #accordion to #acordion1 to 14 because it was necessary for each #accordion to function rather than just the first one. All related attributes that has a direct relationship with #accordion needs to be changed as well, I managed to change toggle-parent="#accodion for the sake of functionality once again. So there's 15 functioning accordions, I added a Home tab with a properly designed accordion which you can use as a template if you decide to redesign the other 14 accordions.


2. In order to use Bootstrap components, you make them according to the document.


The OP code wasn't even close to having any tabs, if you referred to the Bootstrap documents or even W3School's short tutorials, you'd know that you are required to have an <a> for each tab, so your code was short of 16 <a> to toggle 16 tabs. This is why your page is only showing the first tab of 16 tabs, it isn't because the browser just fails midway.

3. Another invalid thing I noticed was that the attribute readonly (and required to a lesser extent) was applied to almost every form control.


Why would you need the readonly attribute on a <select> tag? When assigning attributes to elements, don't start adding a ton of attributes to everything. The clutter makes readability, maintenance, and debugging impossible.


4. There are 2 Plunks:


  1. Plunk 1 is the solution to the OP (Original Post) question which is explained in detail in Part 2 of this answer. The HTML has been partially fixed, I don't have enough time to fix everything.

  2. It has 16 tabs and 15 accordions that work.

  3. Load time has been reduced from 34 sec to 2 sec. with Edge. It appears that Edge heroically tries to make sense of the HTML that was parsed and then fails. The real browsers like Firefox and Chrome justs dumps it and leaves it there.

  4. Plunk 2 is the HTML from the OP code and my solution loading it.

  5. The results are the same, OP code is failing due to the code itself, not because of a loading problem.


Part 2 - A stable way to parse a huge string into HTML. Not needed if OP code actually worked.


OP experiencing heavy latency when attempting to add a huge amount of markup to DOM by innerHTML. Up to 34 seconds to render it completely using Edge while other browsers OP reported at 3 seconds.

I got the load time down to 2 to 3 seconds on Edge and instantly on the real browsers (Chrome and Firefox).

Although OP had tried using createDocumentFragment() already, I believe it is key to a quick load and parse of said HTML. The other key components that the OP probably didn't use are: insertAdjacentHTML() and Immediately Invoked Function Expression

Using insertAdjacentHTML() method instead of innerHTML property. insertAdjacentHTML() is a powerful and versatile version of innerHTML.

Similarities:

  • Both will take a given string and parse as HTML.

  • Both are quick.

Differences:

insertAdjacentHTML() inserts HTML into the DOM, it doesn't overwrite any existing HTML in an element or anywhere in the DOM. innerHTML overwrites the inside of an element.

innerHTML is directed by reference to an element to which it will take a string and overwrite all content of said element with the given string. If innerHTML is just directed to an element without a string, then it will return with the HTML content of said element. innerHTML ability to GET is the only thing insertAdjacentHTML() it can't do. In contrast, insertAdjacentHTML() ability to SET is powerful as explained: insertAdjacentHTML() is directed not only by reference to an element it is told exactly where to go in relation to the referenced element by its first parameter which is one of the 4 DOMStrings that correlate to a position:

"beforebegin" places the string right before the beginning of the element.

      `$elector.before(str)`★

"afterend" places the string right after the end of the element.

      `$elector.after(str)`★

"afterbegin" places the string within the element right after the brginning. In other words the string is inserted before the element's contents.

     `$elector.prepend(str)`★

"beforeend" places the string within the element right before the end. Basically the string is placed after the element's content. This position is the most optimized for speed since the are no proceeding siblings to slow things down.

     `$elector.append(str)`★

insertAdjacentHTML() second parameter is the string that will be parsed into HTML. Using Template Literals instead of literal strings allows us to a whole extra level of easy string manipulation.

    `element.insertAdjacentHTML("beforeend", <input id="${ID+i}" type="${typeArr[i]}" value="${Math.floor(Math.random() * i}">)`

Immediately Invoked Function Expression is a function with a special pattern.

  • It is usually two anonymous functions:

  • The outer function is wrapped in parenthesis.

  • The inner function usually forms a closure.

  • Anonymous/expression functions are created when evaluated and then they are immediately invoked due to the extra parenthesis wrapped around them.

  • They have no name and the variables that the inner function uses can only be accessed by the outer function because they are local scope.

These conditions make an IIFE a one time thing. The signature of an IIFE have slight variances in their signature but the gist of one goes like this:

`(function() { var x = function() {...} x})();`

DOM manipulation is processor intensive and the more we avoid it the better. DocumentFragment was made in order for us to do all of the menial yet numerous tasks involving the DOM -- off of the DOM. We can add as many elements, text, attributes, set event handlers, etc. to the DocumentFragment and its descendants without touching the DOM. Once everything is complete, only one DOM operation needs to be done:

 `document.body.appendChild(frag);` 

Demo - if you'd like to test an actual working Demo, review this Plunk

<!DOCTYPE html>
<html>

<head>
  <meta charset='utf-8'>
  <title>Monstrosity</title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/css/bootstrap.min.css" />
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" />
</head>

<body>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/js/bootstrap.min.js"></script>
  <script>
    (function() {
      const str = `Huge Disaster of HTML`
      const frag = document.createDocumentFragment();
      const node = document.createElement('div');
      let build = function(node, str, frag) {
        node.insertAdjacentHTML('beforeend', str);
        frag.appendChild(node);
        document.body.appendChild(frag);
      }
      build(node, str, frag);
    }());
  </script>
</body>

</html>
zer00ne
  • 41,936
  • 6
  • 41
  • 68
  • Thanks for the elaborate answer. You are right about the duplicate ID's, however, while it's bad practice, it doesn't break the parsing and rendering of HTML. I fixed the duplicate ID's which made no real performance difference. I will still try your other suggestions at a later point to further improve performance, but I solved the real performance hog (see my own answer). – Sander Muller Apr 04 '18 at 20:51
  • I had similar issue and would like to share my experience. Since this here is the best analyse I found while searching a solution, I'll post here hoping this comment might help others. Heavy HTML (7mb), ajax-loaded, causing both Edge and Chrome to break on an "out of memory" error page, whichever way it was inserted: $().html(...), $().append(), js fragment, etc. It finally appeared that both Edge and Chrome seem unable to handle a big html fragment with no linebreaks! Note that Firefox did it like a charm. – fpierrat Sep 15 '22 at 14:13
  • That's interesting that no linebreaks breaks WebKit browsers, but not surprising that jQuery methods crap out under a heavy load. – zer00ne Sep 15 '22 at 15:59
1

Simply appending or inserting the HTML you have provided into the browser does not seem to have any adverse affects. At least for me on my browser/computer. (chrome)

Run this example and see if you experience any delays or pauses..

See Example

    document.getElementById("test").innerHTML = toAppend;

Obviously this is not a complete test because I am missing your CSS and I have modified your html slightly by removing line breaks so that I could assign the text to a variable in the text editor.

If the example works for you then we need to investigate further the data coming from the server, and try adding css to the equation etc..

If the example is causing delays then the problem is likely hardware related, maybe you don't have enough available memory and or cpu which is crippling the browser.

Trevor
  • 16,080
  • 9
  • 52
  • 83
1

I fixed the issue with the help of the answers of Trevor and Zer00ne, but it was something completely different.

The issue was caused by the Laravel Debugbar which tracks AJAX requests by default, and parses the response for debugging purposes. Disabling AJAX request tracking in the Debugbar config solved the issue.