Originally I had a huge div with many child elements that was display: none and then I would simply set it to display: '' and the entire div would be visible. This created some noticable lag. I want to throttle it by displaying the elements one by one with a timeout but the function I created causes strange behavior. It actually works fine if you remove the setTimeout but without setTimeout there is still the same lag.
function throttleDisplay(page) {
page.style.display = '';
var children = page.children;
if (!children.length) return;
for (var i = 0; i < children.length; i++) {
var child = children[i];
setTimeout(function() {
throttleDisplay(child);
}, 100);
}
}
Several problems.
Revealing the top-level <div> all-at-once by setting display:block (or display:'') will trigger just one page re-flow and re-paint, and will therefore create less "lag" than recursively revealing children, which will thrash your layout with exponential re-flows and re-paints.
setTimeout (and therefore its callback) is called for each child in the for loop (at one recursion tier) more or less simultaneously, so this throttles the reveal of descendant elements, not sibling elements.
Unless every element in the tree begins with display:none, setting the top-level element to display:'' will reveal the tree all-at-once, anyway.
Are you certain that revealing the top-level <div> is the cause of your lag? A code sample might help the community find the source of your problem. A first suggestion would be to wrap the code that changes display inside a requestAnimationFrame. (MDN on rAF)
Note 1A: I say "exponential" because you are revealing each child separately versus one container element, but of course the number of operations is linear with respect to the total number of descendants, ignoring their relative "container"/"contained" status.
Note 1B: It is not necessarily the case that this code will "thrash" your layout; you are performing a sequence of "writes" to the layout which will probably be automatically batched at the end of a frame by a modern browser, provided all the function calls can be processed within the space of a frame (~17ms), and we are speaking only about the non-throttled sibling reveals. The asynchronous throttling would allow "reads" from other parts of your code, forcing a re-flow, but since the delay is already the length of 5 frames, this is irrelevant. The point is that this code will not reduce "lag" of any kind.
Thanks to this-vidor for explaining some of the problems with the function I had. I don't know what exactly was causing the very strange behavior in my particular situation because I tried to reproduce it with fake data on jsbin and did not have the same problems. I deciding on building a custom function for my particular situation it looks like this
Messages: function (page) {
var messageCount = 0;
var curThrottle = 0;
page.style.display = '';
var children = page.children;
var lastChild = children[children.length - 1];
var lastChildsChildren = lastChild.children;
for (var i = 0; i < lastChildsChildren.length; i++) {
var child = lastChildsChildren[i];
child.style.display = '';
var messages = child.children[child.children.length - 1].children;
for (var j = 0; j < messages.length; j++) {
if (++messageCount%40 === 0) curThrottle += 30;
var message = messages[j];
(function (message) {
setTimeout(function() {
message.style.display = '';
}, curThrottle);
})(message);
}
}
}
Related
I have a doubt about DOM manipulation.
Whether dom-manipulation requires a refresh ? Or when did DOM manipulation requires refreshing? I see some of the sites keep loading while updating some of their parts.
Also,
How does react.js help to avoid this kind of problems while dealing with the front end development?
I have a doubt about DOM manipulation. Whether dom-manipulation requires a refresh ?
It depends on what you mean by "refresh." I can think of at least three possible things you could mean:
"Refresh" like pressing F5 on a page or hitting the reload button
"Refresh" like recalculate the positions of the elements; this is more correctly called "reflow"
"Refresh" as in repaint the elements on the screen ("repaint")
Reload
No, it doesn't, and in fact if you reload the page, the changes you've made to the DOM with your browser-based code will get wiped out. Here's a really simple example of DOM manipulation; no reloading is done until the end, and when it's done you can see that the changes made previously are wiped out (and then, since the code is also reloaded, we're starting from scratch, so it all starts over):
for (let i = 0; i < 5; ++i) {
delayedAdd(i);
}
function delayedAdd(value) {
setTimeout(() => {
// DOM manipulation
const p = document.createElement("p");
p.textContent = value;
document.body.appendChild(p);
if (value === 4) {
// Refresh after the last one -- wipes out what we've done
setTimeout(() => {
location.reload();
}, 800);
}
}, value * 800);
}
Reflow
Some DOM manipulations trigger reflow, yes; or more accurately, doing some things (which might be just getting, say, an element's current clientLeft property value) triggers reflow, and reflow may involve recalculating layout, which can be expensive. Code can cause reflow repeatedly, including causing the layout to be recalculated repeatedly, doing a single series of manipulations. This list from Paul Irish claims to be "What forces layout/reflow. The comprehensive list." and Paul Irish is a deeply-experienced person in this realm and well-regarded in the industry, so the list is likely to be accurate (though no one is perfect, and these things can sometimes change over time). The page also has some guidance on how to avoid reflow (or more accurately, how to avoid layout recalculation).
Here's an example of code causing unnecessary layout recalculation:
const elementLefts = [];
// This loop triggers layout recalculation 10 times
for (let n = 0; n < 10; ++n) {
const span = document.createElement("span");
span.textContent = n;
document.body.appendChild(span);
// Triggers layout recalcuation every time:
elementLefts.push(span.offsetLeft);
}
console.log(elementLefts.join(", "));
We can get that same information after the loop and only trigger a single layout recalculation:
const spans = [];
// This loop triggers layout recalculation 10 times
for (let n = 0; n < 10; ++n) {
const span = document.createElement("span");
span.textContent = n;
document.body.appendChild(span);
spans.push(span);
}
// Triggers one layout recalcuation, because nothing has changed between
// times we get `offsetLeft` from an element
const elementLefts = spans.map(span => span.offsetLeft);
console.log(elementLefts.join(", "));
Repaint
The browser repaints the screen all the time, typically 60 times per second (or even more if the system it's running on has a higher refresh rate) unless it's blocked by a long-running task on the UI thread (which is shared with the main JavaScript code). DOM manipulations don't cause this repaint (though changing the DOM may change what's painted, which might in turn prevent the browser reusing some painting information it had from last time).
Also, How does react.js help to avoid this kind of problems while dealing with the front end development?
They can help with reflows by minimizing layout recalculations by using the knowledge of what causes recalcs and avoiding doing the things that cause them in loops, etc. That said, they're not a magic bullet, and like everything else they have cons as well as pros.
Just playing around a bit, but noticed it's taking way too long for page to load, is there anyway to get it to print out one line at a time instead of having to wait till the entire page is loaded.
limits(){
var a = 0;
for (var i = 0; i < 1000; i++) {
for (var ii = 0; ii < 1000; ii++) {
document.getElementById('foo').innerHTML += "<p>" + a + "</p>";
a * 2;
}
}
}
Now how would I be able to control this better to where regardless of how long it takes to load print as soon as ready and even slowing it down would be fine.
The javascript method window.requestAnimationFrame(callback) will call your callback function on the next animation frame. It's commonly used for animation, and will probably work well for what you're doing.
To modify your code to use requestAnimationFrame, you have to make your function print a small chunk on its own, with a reference to know what chunk to print. If you stored your page contents in an array, for example, that could just be a starting index and a length. Since you are printing the increasing powers of 2, you can just pass in the last power of two and the number of lines you want to print for each run of the function.
You'll also need an exit condition -- a check within limits that if true, returns without requesting the next frame. I simply put a hard cap on the value of a, but you could also check that the index is less than array length (for my array of page contents idea above).
Because requestAnimationFrame passes in a function name as a callback, you can't pass arguments into it. Therefore, you have to use bind to sort of attach the values to the function. Then, within the function, you can access them using this. config is just an object to hold the initial arguments you want the function to have, and then you bind it, which allows you to access them within the function with this.numLines and this.a.
Then, when you request the next frame, you have to bind the values to limits again. If you are alright with keeping the arguments the same, you can just do limits.bind(this). But if you want to change them, you can create another object in a similar way to how I wrote config and bind that instead.
The following code seems to be a basic example of roughly what you're looking for:
var foo = document.getElementById('foo');
var maxA = 1000000000000000000000000000000000;
function limits() {
for(var i=0; i<this.numLines; ++i) {
foo.innerHTML += "<p>" + this.a + "</p>";
this.a *= 2;
if(this.a > maxA) {
return;
}
}
requestAnimationFrame(limits.bind(this));
}
config = {
numLines: 3,
a: 1
};
requestAnimationFrame(limits.bind(config));
And is implemented in JSFiddle here. I've also implemented a version where it puts each line at the top of the page (as opposed to appending it to the bottom), so that you can see it happening better (you can find that one here).
You can do something like this:
limits(){
var a = 0;
for (int i = 0; i < 1000; i++) {
for (int ii = 0; ii < 1000; ii++) {
setTimeout(function(){
document.getElementById('foo').innerHTML += "<p>" + a + "</p>";
a * 2;
}, 0);
}
}
}
You can adjust the time in the setTimeout, but even leaving it as zero will allow a more interactive experience even while the page builds. Setting it to 10 or 100 will of course slow it down considerably if you like.
I'm pulling some strings out of a couple of arrays of objects and trying to layer them in div elements. Here is my code:
function renderBlogs() {
for (i = 0; i < blogArticles.length; i++) {
var currentArticle = blogArticles[i];
var divArticleWrapper = document.createElement("div");
divArticleWrapper.className = "article-wrapper";
var articleTitle = document.createElement("h1");
articleTitle.innerHTML = currentArticle.title;
var articleAuthor = document.createElement("h4");
articleAuthor.innerHTML = currentArticle.author;
var articlePublishedOn = document.createElement("h4");
articlePublishedOn.innerHTML = currentArticle.publishedOn;
var articleURL = document.createElement("a");
var articleText = document.createTextNode(currentArticle.url);
articleURL.appendChild(articleText);
articleURL.href = currentArticle.url;
divArticleWrapper.appendChild(articleTitle);
divArticleWrapper.appendChild(articleAuthor);
divArticleWrapper.appendChild(articlePublishedOn);
divArticleWrapper.appendChild(articleURL)
document.getElementById("blog-container").appendChild(divArticleWrapper);
for (j = 0; j < currentArticle.content.length; j++) {
var currentContent = currentArticle.content[j];
var divContentWrapper = document.createElement("div");
divContentWrapper.className = "content-wrapper";
var contentHeading = document.createElement("h2");
contentHeading.innerHTML = currentContent.heading;
var contentParagraph = document.createElement("p");
contentParagraph.innerHTML = currentContent.paragraph;
divContentWrapper.appendChild(contentHeading);
divContentWrapper.appendChild(contentParagraph);
divArticleWrapper.appendChild(divContentWrapper);
};
};
This works fine for "article-wrapper", but in "content-wrapper", the div wraps around each individual paragraph element instead of wrapping around all three, like so:
<div class="content-wrapper">
<h2>Slow</h2>
<p>Is changing the DOM slow? What about loading a <script> in the
<head>? JavaScript animations are slower than CSS ones, right?
Also, does a 20-millisecond operation take too long? What about 0.5
seconds? 10 seconds?</p>
</div>
<div class="content-wrapper">
<p>While it’s true that different operations take different amounts of
time to complete, it’s hard to say objectively whether something is slow
or fast without the context of when it’s happening. For example, code
running during idle time, in a touch handler or in the hot path of a game
loop each has different performance requirements. Put another way, the
people using your website or app have different performance expectations
for each of those contexts. Like every aspect of UX, we build for our
users, and what they perceive is what matters most. In fact, number one
on Google’s ten things we know to be true is “Focus on the user and all
else will follow.”</p>
</div>
<div class="content-wrapper">
<p>Asking “What does slow mean?,” then, is really the wrong question.
Instead, we need to ask “What does the user feel when they’re interacting
with the things we build?”</p>
</div>
I've tinkered around and I'm not quite sure what's wrong.
You are creating multiple divs. You need to create content-wrapper outside for and append paragraphs inside the loop.
I'm currently updating the content of alot of divs using:
https://jsfiddle.net/foreyez/ctpuaw5v/
for (var i = 0; i < 400; i++)
{
document.getElementById('box' + i).innerHTML = 'a'; // each element can have a different value, a is arbitrary
}
(400 is arbitrary, it could be alot more)
I'm wondering as far as browser reflow, would it do a reflow on each innerHTML set, and if so, maybe there's a way for me to update all divs at once with only one reflow (or even NO reflow) for performance reasons, or maybe use something faster than innerHTML.
If you can replace 400 calls of elem.innerHTML = ...; by single call of
container.innerHTML = cummulativeHtml; it will be faster - only one DOM mutation transaction instead of 400.
If you use jquery you can just do something like:
$('.box').html('whatever');
I have 2000 rows of data as follows
<div class="rr cf">
<span>VLKN DR EXP</span>
<span>01046</span>
<span>VELANKANNI</span>
<span>20:30</span>
<span>DADAR</span>
<span>10:00</span>
</div>
On a button click I am checking for text within them and updating the display of each row to block or none. The code that does this is
$('.rr').each(function(){
this.style.display="block";
});
var nodes = $(".rr");
for(var i=0;i < nodes.length; i++) {
// if data found
nodes.get(i).style.display="block";
// else
nodes.get(i).style.display="none";
}
This seems to be possibly very slow. I get chrome alert box as to kill the page.
Any ideas? what optimization can I do here?
Local Variables and Loops
Another simple way to improve the performance of a loop is to
decrement the iterator toward 0 rather than incrementing toward the
total length. Making this simple change can result in savings of up to
50% off the original execution time, depending on the complexity of
each iteration.
Taken from: http://oreilly.com/server-administration/excerpts/even-faster-websites/writing-efficient-javascript.html
Try saving the nodes.length as a local variable so that the loop doesn't have to compute it each time.
Also, you can store nodes.get(i) into a local variable to save some time if you are accessing that data a lot.
If the order isn't important, consider decrementing your for loop towards 0.
jQuery's each() loop is a bit slower than looping through the set yourself as well. You can see here that there is a clear difference.
Very simple example
You'll see that in my example, I've condensed the loop into a while loop:
var nodes = $(".rr span");
var i = nodes.length;
while(i--){
if(i%2 === 0){
nodes.get(i).style.color = "blue";}
}
Notice that the while loop decrements i through each iteration. This way when i = 0, the loop will exit, because while(0) evaluates to false.
"Chunking" the Array
The chunk() function is designed to process an array in small chunks
(hence the name), and accepts three arguments: a “to do” list of
items, the function to process each item, and an optional context
variable for setting the value of this within the process() function.
A timer is used to delay the processing of each item (100ms in this
case, but feel free to alter for your specific use). Each time
through, the first item in the array is removed and passed to the
process() function. If there’s still items left to process, another
timer is used to repeat the process.
Have a look at Nick Zakas's chunk method defined here, if you need to run the loop in sections to reduce the chance of crashing the browser:
function chunk(array, process, context){
setTimeout(function(){
var item = array.shift();
process.call(context, item);
if (array.length > 0){
setTimeout(arguments.callee, 100);
}
}, 100);
}
Using createDocumentFragment()
Since the document fragment is in memory and not part of the main DOM
tree, appending children to it does not cause page reflow (computation
of element's position and geometry). Consequently, using document
fragments often results in better performance.
DocumentFragment are supported in all browsers, even Internet Explorer
6, so there is no reason to not use them.
Reflow is the process by which the geometry of the layout engine's
formatting objects are computed.
Since you are changing the display property of these elements iteratively, the page mush 'repaint' the window for each change. If you use createDocumentFragment and make all the changes there, then push those to the DOM, you drastically reduce the amount of repainting necessary.
Firstly, where are the delays occurring - in the jquery code, or the data check? If it is the jquery, you could try detaching the data container element (ie the html element that contains all the .rr divs) from the DOM, make your changes, and then re-attach it. This will stop the browser re-processing the DOM after each change.
I would try
1) set display of the common parent element of all those divs, to "none"
2) loop through divs, setting display as appropriate
3) set parent element display back to block
I believe this will help because it gives the browser the opportunity to aggregate the rendering updates, instead of forcing it to fully complete each single time you change the display property. The visibility of all the child node are irrelevant if the parent isnt displayed, so the browser no longer has a need to render a change in each child until the parent becomes visible again.
ALso, I fail to see the purpose of you first looping through them all and setting them all to block before you loop again and set them to their intended value.
Don't use jQuery here, jQuery will just slow things down here.
var elements = document.getElementsByClassName('rr'),
len = elements.length;
for(var i = 0; i < len; i++)
{
var ele = elements[i];
if(ele.innerHTML.search(/01046/) != -1)
ele.style.display = "none";
}
This should be much faster.
I'm also having performance problems while looping through roughly 1500 items.
As you might have guessed, the loop itself isn't the bottleneck. It's the operation you do within it that's the problem.
So, what I migrated the load using setTimeout. Not the prettiest of solutions but it makes the browser responsive between the updates.
var _timeout_ = 0;
for(var i=0;i < nodes.length; i++)
{
setTimeout(
(function(i)
{
return function()
{
if(stuff)
{
nodes.get(i).style.display="block";
}
else
{
nodes.get(i).style.display="none";
}
}
})(i),
_timeout_
);
_timeout_ += 4;
}
This will delay every update with 4 milliseconds, if the operation takes longer, the browser will become unresponsive. If the operation takes only 2 milisecond on your slowest browser, you can set it to 3, etc. just play around with it.