jQuery append in loop - DOM does not update until the end - javascript

When looping over a large collection and appending it to the DOM, the DOM only refreshes after all items have been appended. Why doesn't the DOM update after each append() call? Can I force the DOM to refresh after each append (or maybe after each n number of appends)?
var i = 0;
for (i=0; i<5000; i++) {
$('#collection').append('<li>Line Item</li>');
}
Link to jsfiddle
NOTE: I understand that better performance (avoiding DOM reflow) can be achieved by appending all elements to a local variable, and then appending that variable to the DOM. But I want the first n elements to render on the screen, then the next n, etc. until all elements are rendered.

Most everything in the browser stops while a Javascript is running. This includes for example DOM rendering, event handling, image animation.
The only way to cause the DOM to be rendered is to end the Javascript. You can use the setTimeout method to start code again after the update:
var i = 0;
function appendSomeItems() {
for (var j=0; j<50; i++, j++) {
$('#collection').append('<li>Line Item</li>');
}
if (i < 5000) window.setTimeout(appendSomeItems, 0);
}
appendSomeItems();
Demo: http://jsfiddle.net/Uf4N6/

you need to give back control to the browser every once in a while:
var current = 0
function draw() {
var next = current + 10
for(var i = current; i < next; i++) {
$('#collection').append('<li>Line Item</li>');
}
current = next
setTimeout(draw, 50);
}
draw();

Generally, please don't touch DOM too many times. It is a performance killer as you have already observed.
var result="";
for (i=0; i<5000; i++) {
result+='<li>Line Item</li>';
}
$('#collection').append(result);
In this way, you touch DOM only once!
An alternative way is using array.
var result=[];
for (i=0; i<5000; i++) {
result.push('<li>Line Item</li>');
}
$('#collection').append(result.join(""));

If that's really what you want to do...
var i = 0;
for (i=0; i<5000; i++) {
setTimeout(function() {
$('#collection').append('<li>Line Item</li>');
}, 0);
}

Related

Length of getElementsByClassName() array changes when removing class

I'm trying to improve my vanilla JavaScript and was trying to write a function that removes the class "active" from a list item and a corresponding div. My code is as follows:
var elems = document.getElementsByClassName("active");
console.log(elems.length);
for (var i = 0; i < elems.length; i += 1) {
elems[i].classList.remove("active");
console.log("end", elems.length);
}
When I first log elems.length I get 2. After the first time through the loop elems.length is now 1. This obviously causes a problem in the functionality of the code.
Why is using classList.remove causing the length of elems to change? How can I get the proper functionality without using jQuery?
getElementsByClassName() returns a live collection; it will change as fewer (or more) elements match it.
The easiest way around this is to do your removals in reverse:
var elems = document.getElementsByClassName("active");
for (var i = elems.length - 1; i >= 0; i--) {
elems[i].classList.remove("active");
}

Jquery .clone() method. Removing clones

I noticed some interesting behavior when dealing with the .clone() function.
If I have a function to create rows and columns dynamically like this:
function appendDiv(n) {
for (var i=0;i<n;i++) {
$rows.append($columns.clone()); //assume I put $('.rows') & others in a var
}
for (var i=0;i<n;i++) {
$wrapper.append($rows.clone());
}
}
And I then delete the elements from the DOM maybe like this:
function deleteClones() {
$wrapper.off();
$wrapper.html('');
$('body').append($wrapper);
num = prompt("Enter another number.");
return num;
}
So I'd be calling the functions in an order like this:
appendDiv(num);
num = deleteClones();
appendDiv(num);
Can someone tell me why when I call appendDiv(num); again after removing those elements, the old columns are added along with the new ones? Here is a preschool level demonstration of what I mean: http://jsfiddle.net/wj6sgeeu/. Notice upon inspecting the html document, the clones that were created before we called deleteClones() are added again when we call appendDiv(num) for the second time.
I'm new to jquery, so maybe this is a self evident and obvious fact (maybe using a different method to remove clones?) but does someone have an explanation for this behavior?
Thank you!
You need to remove the previously added columns from the row, else you are just keep adding more columns to the existing columns
function appendDiv(n) {
$rows.empty();
for (var i = 0; i < n; i++) {
$rows.append($columns.clone());
console.log('ok');
}
for (var i = 0; i < n; i++) {
$wrapper.append($rows.clone());
console.log('ok2');
}
}
Demo: Fiddle

Loop through colours array with jQuery

I'm trying to give each div a different background colour. Here is my current code:
http://jsfiddle.net/Uy2FX/2/
var imgColours = ['#FCCF94', '#C4C9E5', '#ADE3D6'];
for (i=0; i < imgColours; i++) {
$('.img').css({backgroundColor: imgColours[0]});
}
However, I'm not quite sure where this is going wrong. I understand that's probably too simple to work, but in my mind it makes sense. Could someone point me in the right direction?
There are some relevant errors in your code.
This is probably what you wanted to do:
// V1 : Basic
var imgColours = ['#FCCF94', '#C4C9E5', '#ADE3D6'];
for (var i=0; i < imgColours.length; i++) {
$('.img:eq('+i+')').css({backgroundColor: imgColours[i]});
}
But if you want to get a random color from your array, for any number of divs, and also optimise your jQuery code a bit for better performance:
// V2 : random colors
var $imgs = $('#boxes1').find('.box'),
imgsCount = $imgs.length,
coloursCount = imgColours.length;
for (var i=0; i < imgsCount; i++) {
var rnd = Math.floor(Math.random() * coloursCount),
color = imgColours[rnd];
$imgs.eq(i).css({backgroundColor: color});
}
Or, if you want to loop through the colours following the order of the array, just change the loop:
// V3 : sequential colors
// Add V2 variables here
for (var i=0; i < imgsCount; i++) {
var color = imgColours[i%coloursCount];
$imgs.eq(i).css({backgroundColor: color});
}
UPDATED FIDDLE: http://jsfiddle.net/Uy2FX/12/
For some very basic tips on jQuery selectors performance: http://www.sitepoint.com/efficient-jquery-selectors/
You are always assigning imgColours[0] to EVERY div. I think what you are looking for is imgColours[i]
You will also need to use imgColours.length to tell your loop how long the array is.
You are also grabbing all HTML elements with the class of img, so this will change all of them each time.
To grab each element separately, you can use the CSS nth-of-type selector. Basically you can just do something like
$(".img:nth-of-type(" + i + ")")
You need to use imgColours.length
The for loop has no idea how long the array is otherwise
Edit: What's the point in this for loop if you end up using imgColours[0] anyways? If you want to loop each color, use i instead of 0.
And either way, this will not achieve a different background per div.
Try selecting by className (I'm going to use vanilla.js because it's simple)
var elements = document.getElementsByClassName("img");
for (var i = 0; i<elements.length; i++) {
var color = imgColours[Math.floor(Math.random()*imgColours.length)]; //get a RANDOM color change me if needed
elements[i].style.backgroundColor = color;
}
How about this?
var ec = 0;
var i = 0;
for(ec; ec < elements.length; ec++, i++) {
elements[ec].style.backgroundColor = imgColours[i];
if(i == (imgColours.length - 1)) i = -1;
}
http://jsfiddle.net/y2dq3/

Inserting html elements while DOM is changing

My code should insert HTML content in all divs that have a predefined class name, without using jQuery and at least compatible with IE8 (so no getElementsbyClass).
The html:
<div class="target">1</div>
<div class="target">2</div>
<div class="target">3</div>
<div class="target">4</div>
The javascript:
var elems = document.getElementsByTagName('*'), i;
for (wwi in elems) {
if((' ' + elems[wwi].className + ' ').indexOf(' ' + "target" + ' ') > -1) {
elems[wwi].innerHTML = "YES";
//elems[wwi].innerHTML = "<div>YES!</div>";
}
}
You can try it here.
As you can see inside each div the word YES is printed. Well the if you comment elems[wwi].innerHTML = "YES"; and replace that for elems[wwi].innerHTML = "<div>YES!</div>" the code fails. I suppose is because inserting div elements modify the DOM and in consequence the FOR cycle fails. Am i right?
Well i can solve this pretty ugly by recalling the for cycle each time i make an innerHTML, and when i insert the code i can add a class (like data-codeAlreadyInserted=1) to ignore the next time the FOR pass in that div. But again, this is pretty much a very bad solution since for an average site with many tags I can even freeze the user browser.
What do you think? lets suppose i dont know the amount of tags i insert on each innerHTML call.
"I suppose is because inserting div elements modify the DOM and in consequence the FOR cycle fails. Am i right?"
Pretty much. Your elems list is a live list that is updated when the DOM changes. Because you're adding a new div on every iteration, the list keeps growing and so you never get to the end.
To avoid this, you can either do a reverse iteration,
for (var i = elems.length-1; i > -1; i--) {
// your code
}
or convert the list to an Array.
var arr = [];
for (var i = 0, len = list.length; i < len; i++) {
arr.push(elems[i]);
}
for (i = 0; i < len; i++) {
// your code
}
Another way is to use replaceChild instead of innerHTML. It works better and it's way faster:
var newEl = elem[wwi].cloneNode(false);
newEl.innerHTML = html;
elem[wwi].parentNode.replaceChild(newEl, elem[wwi]);
You can take a copy of the live node list:
var nodes = [];
for (var i = 0, n = elems.length; i < n; ++i) {
nodes.push(elems[i]);
}
and then use a proper for loop, not for ... in to iterate over the array:
for (var i = 0, n = nodes.length; i < n; ++i) {
...
}
for ... in should only be used on objects, not arrays.

Javascript pause in WinRT for animation

I want to pause execution in javascript so that I can animate the appearance of text on the screen. My code is currently this:
function AnimateWord(word) {
for (var i = 0; i <= word.length; i++) {
myTest.textContent += word.charAt(i);
// need to pause here and then continue
}
}
I've done some research and the preferred method to do this in javascript seems to be setTimeout, although I can't really see how that would work in this case without creating a recursive loop (which just doesn't seem like the right solution to me).
Is there a way around this, or am I stuck with setTimeout and a recursive loop?
EDIT:
Based on some additional tests, I've tried using the Promise.timeout:
for (var i = 0; i <= word.length; i++) {
WinJS.Promise.timeout(1000).then(TypeLetter(word.charAt(i)));
}
function TypeLetter(letter) {
myTest.textContent += letter;
}
But this doesn't seem to actually pause. In fact, it seems to completely ignore the timeout. I've also tried:
setTimeout(TypeLetter(word.charAt(i)), 1000);
With basically the same results. this page seems to imply that it should wait and then execute the task. I'm quite new to WinJS, but am equating a promise to an await keyword in C#.
setTimeout/setIngerval/requestAnimationFrame are pretty much your only choices. I wouldn't call them recursive perse - while you do call your same function over & over. The calls tack is completely independent.
What kind of animation are you really trying to create? It may be better to create a span for each character, have them hidden, and then fade/translate the, in using CSS animations.
var i = 0;
var str = "plz send teh codez";
var intervalId = setInterval(function(){
myTest.textContent += str.charAt(i);
if (++i >= str.length)
clearInterval(intervalId);
}, 1000);
demo http://jsfiddle.net/qxfVu/1/
Does this do what you are looking for:
var array1 = [];
function AnimateWord(word) {
var test = $('#test');
for (var i = 0; i <= word.length; i++) {
array1.push(word.charAt(i));
test.delay(100).queue(function() {
this.innerHTML += array1[0];
array1.splice(0, 1);
$(this).dequeue();
});
}
}
please see fiddle link as well: http://jsfiddle.net/7Ea9u/

Categories

Resources