I have some code that looks like this
//create a long string of html, which includes a div with id="mydiv"
someElement.innerHTML = s; //s is the string above
document.getElementById('mydiv')
Now, after I set the innerHTML, it is going to take a while for the browser to actual render the DOM that includes id="mydiv" . So, will the javascript sit and wait for the dom to be properly rendered after the innerHTML injection, or will it move right along and call the getElementById which is now unsafe since the DOM for that id may not be created yet?
Here's an example that inserts nearly 12,000 elements into the DOM using innerHTML, then calls getElementById() to find the one element at the end that has an ID.
It successfully finds the element.
Example: http://jsfiddle.net/fzUUU/
There is no standard for innerHTML. So literally anything is allowed to happen including not being implemented (I believe Opera toyed with this once). It's just a hack introduced by Microsoft in IE.
However. Implementing innerHTML in any way that diverges from the way IE does it will break lots of pages on the web. So browser makers are forced to implement it the way IE does it. And here's how IE does it: when innerHTML is running the script interpreter stops. That is to say innerHTML blocks until the DOM is fully parsed.
So it should be safe to access the DOM directly after innerHTML for all current browsers and, due to the number of pages on the web that requires it, in the foreseeable future.
Additional answer:
It appears that the draft HTML5 spec specifies innerHTML: http://www.w3.org/TR/2008/WD-html5-20080610/dom.html#innerhtml0
It basically describes what IE already does. It should be noted that it doesn't specifically say weather the operation is bolcking or non-blocking but as mentioned earlier a non-blocking implementation will break lots of pages on the web.
Related
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.
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
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:
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.
It has 16 tabs and 15 accordions that work.
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.
Plunk 2 is the HTML from the OP code and my solution loading it.
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>
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.
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.
I'm looking at ways to improve performance of my single page web app, which will need to run on a variety of devices including lower-end phones.
I've got 8 modals (Twitter Bootstrap but this question applies to any framework) that add over 200 elements to my total page DOM element count (783). Is it worth having these as strings in Javascript rather than code in the HTML, and injecting them when needed into the DOM immediately before display, then removing them afterward? Would strip live DOM size down by a quarter, thus making e.g. JQuery element searches quicker, lighterweight page etc.
I was thinking to use JQuery's $.detach() and $.append() for example
Anytime you modify the DOM, you take a performance hit because the browser will have to "reflow" and "repaint" the UI. As such, keeping those modifications to a minimum will help, however doing modifications in batches absorbs some of that performance hit (i.e. changing 3 DOM elements separately is more expensive than changing all 3 at once). So, group together your DOM changes as best you can.
The actual mechanism you use to inject the new content could either be by:
Passing a string of HTML to the HTML parser and asking it to parse
on demand the string. This is essentially the same as the process
that happens when the page is being parsed from the server. Using
the standard .innerHTML or JQuery .html() accomplishes this.
You could also build up the DOM element in memory first and then
inject that node into the DOM at the right time (i.e. document.createElement or document.createDocumentFragment). I generally favor
this approach as it is more programmatic, vastly reduces the
possibility of string concatenation and quotation errors and is
cleaner to read. From a performance standpoint, this gives you the
benefit of getting some of the work done prior to DOM injection
time. This would be the equivalent of the DOM .appendChild() or
the JQuery .append() methods.
In the end, today's modern user agents handle DOM changes much better than they used to and either approach is viable. It's the really bad techniques (like modifying the DOM in a loop) that you want to stay away from that, in the end, will make a difference.
I am thinking about experimenting with a custom stylesheet. One big issue that I've seen for a long time is the simple fact that we only have h1 and p tags for typography.
It would make a lot more sense to (me to) have descriptive tags such as <hero>, <copy>, <micro>, <mini> etc that can be used instead of doing weird things like <h1 class='hero'>.
What actually happens with evergreen browsers & IE 10+ if you just define a new tag? It does work in general at least in chrome to just define a new tag and assign some CSS properties to it. However, will there be limitations on how we could use Javascript on a tag defined like this? Are there any big downsides?
I am not considering defining a webcomponent for <hero> since that would need to register a component whenever it gets attached which I'm sure would be heavy on performance for something as simple as a heading hero tag. Last time I remembered the html5shiv did something like this for IE8 or IE9. When it wouldn't know the tag it would just convert the tag to a block level element I think with standard properties. Is this what is happening in all evergreen browsers as of now, meaning as long as we don't need special events, methods and properties defined on a tag it would be ok to just write tags such as <hero>?
The the browser will stick the unrecognised element in the DOM with some default styling on the assumption that it is a new feature (and in the hope that there will be a CSS/JS shim to add backwards compatibility) rather than falling over because it's been given invalid HTML.
(Then, in the future, an element of that name does get added to the spec, and your page breaks).
Don't write invalid HTML.
Based on a click event on the page, via ajax I fetch a block of html and script, I am able to take the script element and append it to the head element, however WebKit based browsers are not treating it as script (ie. I cannot invoke a function declared in the appended script).
Using the Chrome Developer Tools I can see that my script node is indeed there, but it shows up differently then a script block that is not added dynamically, a non-dynamic script has a text child element and I cannot figure out a way to duplicate this for the dynamic script.
Any ideas or better ways to be doing this? The driving force is there is potentially a lot of html and script that would never be needed unless a user clicks on a particular tab, in which case the relevant content (and script) would be loaded. Thanks!
You could try using jQuery... it provides a method called .getScript that will load the JavaScript dynamically in the proper way. And it works fine in all well known browsers.
How about calling eval() on the content you receive from the server? Of course, you have to cut off the <script> and </script> parts.
If you're using a library like jQuery just use the built-in methods for doing this.
Otherwise you'd need to append it to the document rather than the head like this:
document.write("<scr" + "ipt type=\"text/javascript\" src=\"http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js\"></scr" + "ipt>");
In all honesty, I have no idea why the script tag is cut like that, but a lot of examples do that so there's probably a good reason.
You'll also need to account for the fact that loading the script might take quite a while, so after you've appended this to the body you should set up a timer that checks if the script is loaded. This can be achieved with a simple typeof check on any global variable the script exports.
Or you could just do an eval() on the actual javascript body, but there might be some caveats.
Generally speaking though, I'd leave this kind of thing up to the browser cache and just load the javascript on the page that your tabs are on. Just try not to use any onload events, but rather call whatever initializers you need when the tab is displayed.
Is it possible to modify the DOM during or before the initial DOM parsing? Or do I have to wait until the DOM is parsed and built before interacting with it? More specifically, is it possible to hinder a script element in DOM from running using userscripts/content scripts or similar in chrome or firefox?
Tried using eventListeners on DOMNodeInserted before the DOM is parsed, but these are only fired after the DOM is built.
These are two separate questions:
1. Is it possible to modify the DOM during or before the initial DOM parsing?
Yes. As soon as the browser builds the root element, then you can start querying and mutating the DOM. Note that when your script runs, some of the page may still yet be unparsed, perhaps even still in transit on the network. Your script generally has access to any element declared in the source before the script tag containing/calling your script. This includes parent elements containing your script tag.
2. Is it possible to hinder a script element in DOM from running using userscripts/content scripts or similar in chrome or firefox?
No. All scripts are executed and one script can't prevent another script's initial execution. However, you can perhaps go back through and remove event handlers, and otherwise attempt to counteract the effects of a script. Although this scenario seems a bit shady and/or against the grain of normal JavaScript usage.
I've actually looked into this a lot while developing an adblocker for Chrome. Basically, you can't use just Javascript to stop Javascript, especially since it would run simultaneously with the script element in question. So even if it would work it would cause a race condition.
you can modify elements during document parsing, but after than this element has added to dom.
for example
<div id="example"></div>
<script>
document.getElementById('example').innerHTML = 'hello';
</script>
<div>something else</div>
Is it possible to modify the DOM during or before the initial DOM parsing?
Yes it is possible.
Start by completely preventing the document from getting parsed altogether then on the side, fetch the same document, do any processing on this document and then inject the resulting document in the page.
Here is how I currently do just that https://stackoverflow.com/a/36097573/6085033