Fast way to append large list to dom - javascript

I am trying to populate an empty list using an array of photo names. I have made this work already, but with a large amount of photos it can become relatively slow. More than 500 photos is not unusual.
I am wondering if someone could find a way to make this code work faster, or tell me what make this code run slow so I can have another look at it myself.
The code I have is as follows. this.photoListElement is a jQuery object referring to the unordered list element. photoNames is an array of photo name strings. Variables are declared at the top of the function which is not shown here. isThumbDownloaded checks a variable in an object. getThumb and getThumbId are functions which add some strings together.
(...)
place = [];
for(i=0; i< photoNames.length; ++i) {
photoName = photoNames[i];
if(coverages.isThumbDownloaded(coverageId, photoName)){ // A function which checks if a photo is downloaded. If it is, the photo should not be hidden, and the right background should be applied.
bg = 'background-image:url(\''+coverages.getThumb(coverageId, photoName) + '?' + new Date().getTime()+'\');';
cls = '';
} else {
bg = '';
cls = 'hidden';
}
place[i] = '<div id="'+ this.getThumbId(photoName) +'" photoName="'+photoName+'" style="'+bg+'" class="phoPhoto '+cls+'" onclick="$.mobile.changePage($(\'#carousel\'), {transition: \'slidefade\'})"></div>';
}
this.photoListElement.html(place.join(''));
(...)
Help is very much appreciated.
After research
The problem is not the loop, although some minor optimizations can be done, but the the dom insertion.

In the loop you are counting the number of photoNames on each iteration:
for(i=0; i< photoNames.length; ++i)
Keep the length in a variable instead and the loop will be faster:
for (var i = 0, ilen = photoNames.length; i < ilen; i += 1)
Also, string concatenation might be faster than array join, check this jsperf .
So:
place = "";
...
place += '<div>.......';
..
this.photoListElement.html(place);

Try using
$('ul').append(ilen);

Related

Type Writter effect with HTML tags inside the text

I am trying to achieve typing effect which i did but the problem is that the text i have and is being typed inside a <p> tag as it's inner HTML has also some HTML tags like <span></span> and because the typing is char by char it gets typed out is <span> instead of rendered as the element. Is there any way to achieve the effect while keeping the HTML tags as they are? I have highlighted words that are wrapped in spans what i ultimately want to achieve is have the words typed out as highlighted...
function typeWriter() {
if (i < txt.length) {
document.getElementById("content_html").innerHTML += txt.charAt(i);
i++;
setTimeout(typeWriter, self.typing.speed);
}
}
typeWriter();
Here is an example of a text i am trying to type out :
<p>Surface chemistry deals with phenomena that occur at the surfaces or interfaces. Many important phenomena, noticeable amongst this being corrosion, electrode processes, heterogeneous catalysis, dissolution, and crystallization occur at interfaces. The subject of surface chemistry finds many applications in industry, analytical work, and daily life situations.</p>
It contains a <p> tag which can in the future change to any other HTML tag since this is coming from the server...
I don't fully understand your question, but you can also achieve a typing animation with pure CSS as shown in this Codepen which could remove some of the issues you are having.
animation: typing 7s steps(15, end), /* # of steps = # of chars */
blink-caret .5s step-end infinite alternate;
I belive you are looking for
document.getElementById("demo").innerText = "<p>asd</p>";
instead of innerHTML
This way it will render exacly "<p>asd</p>" instead of creating an element
Try This out Where I Am Rendering The Whole Text After You Have Appended Everything In It.
<script>
var i = 0;
txt = "<p>test this out</p>";
function typeWriter() {
if (i < txt.length) {
document.getElementById("content_html").innerHTML += txt.charAt(i);
i++;
setTimeout(typeWriter, 100);
} else {
document.getElementById("content_html").innerHTML = document.getElementById("content_html").innerText;
}
}
typeWriter();
</script>
So I wanted to make it myself because task is interesting. I know that #little_coder has already provide the best answer in comments to question.
This is what I have done:
js:
// text
txt = "asd<p>test this out<span>with more </span>and between text<span class='second'>second</span></p>afe";
//shared array
var instructions = [] ;
// typeWriter
var i = 0; //
var j = 0;
var elem = '';
var elem_value = '';
var speed = 50;
function typeWriter() {
if(j < instructions.length){
if (typeof instructions[j][1] == 'string'){
if (i < txt.length) {
instructions[j][0].innerHTML += instructions[j][1].charAt(i);
i++;
setTimeout(typeWriter, speed);
}else{
j=j+1;
i = 0;
setTimeout(typeWriter, speed);
}
}
else if(typeof instructions[j][1] == 'object'){
console.log("ins", instructions[j][0]);
instructions[j][0].appendChild(instructions[j][1]);
j=j+1;
i=0;
typeWriter();
}
}
}
//
// recreateNode
parser = new DOMParser();
function recreateNode(list, container){
doc = parser.parseFromString(list, "text/html");
doc.body.childNodes.forEach(function(a){
console.log(a);
if(a.nodeName == '#text'){
instructions.push([container, a.nodeValue])
}
else{ // if there is element to create
b = a.cloneNode(true); // handle deep elements
c = a.cloneNode(false); // this way I can get ONLY the element with attributes and classes
/* container.appendChild(c) */; // I append only element
instructions.push([container, c]);
recreateNode(b.innerHTML, c); // b will be appended to c
}
});
}
// init
parent = document.getElementById("content_html")
recreateNode(txt, parent);
typeWriter();
First I created recreateNode that creates instructions array that stores steps for recreating html structure of the text. Using this instructions in typeWriter I make typing effect or create html element. Keeping in instructions container value I know where to put next text.
Everything would be ok but when it comes to .appendChild or at least I think this is the cause... appending elements takes time that makes typing effect not being fluent.
js fiddle: https://jsfiddle.net/an5gzL7f/3/
Thanks in advance for any tips how it could work fast
I just got through a similar challenge while updating TypeIt (https://typeitjs.com) so that it can handle typing nested HTML. In short, the approach I used was repeatedly traversing over all the childNodes of the parent element, pulling out each nested node and expanding it into an array of queue items to be later typed.
It got a little hairy working through it all, but if it's helpful, you're welcome to search through the code to see how I'm doing it:
https://github.com/alexmacarthur/typeit/blob/master/src/helpers/chunkStrings.js#L98
A lot of it is very TypeIt-specific -- particularly how I construct objects whose properties contain the information needed to reconstruct those elements.
The most elegant solution is probably the one using the CSS only. However it comes with the downside, that if the text has a line break or is not written in a monospace font, it's basically unpracticable. I took the liberty to write a patch of code to accompany the rest of the solutions that are all useful in their own way.
The downside to my solution is that i used the textContent property, which will not render as HTML. I refuse to use innerHTML for security reasons. So adding <a> tags is probably not the best idea at the moment.
The goal of my approach is to use the window.requestAnimationFrame hook, because it's so powerful. And the rest of the code is still a bit cluttered but at least it's self explanatory.
let paragraphNode = document.querySelector('p');
let textObject = {
actionCounter: 0,
typeDelay: 6,
cursorDelay: 22,
cursorShow: false,
currentIndex: 0,
stringLength: paragraphNode.textContent.length,
textContent: paragraphNode.textContent
}
paragraphNode.textContent = "";
const writeText = ()=>{
textObject.actionCounter ++;
if( textObject.actionCounter % textObject.typeDelay == 0 ){
textObject.currentIndex += 1;
}
let string = textObject.textContent.substring(0, textObject.currentIndex);
if( textObject.actionCounter % textObject.cursorDelay == 0 ){
textObject.cursorShow = !textObject.cursorShow;
}
paragraphNode.textContent = (textObject.cursorShow) ? string + "|" : string;
window.requestAnimationFrame(writeText);
}
window.requestAnimationFrame(writeText);
Here is the codepen example i worked out. The only thing missing at the time of writing is a person brave enough to replace textContent with innerHTML and possibly check if the next letter is a '<' and add the whole tag at once. Might be tricky though. I strongly recommend against it.
typewriter on codepen

Javascript performance array of objects preassignment vs direct use

I have a doubt about how can be affected to speed the use of object data arrays, that is, use it directly or preasign them to simple vars.
I have an array of elements, for example 1000 elements.
Every array item is an object with 10 properties (for example).
And finally I use some of this properties to do 10 calculations.
So I have APPROACH1
var nn = myarray.lenght;
var a1,a2,a3,a4 ... a10;
var cal1,cal2,.. cal10
for (var x=0;x<nn;x++)
{ // assignment
a1=my_array[x].data1;
..
a10 =my_array[x].data10;
// calculations
cal1 = a1*a10 +a2*Math.abs(a3);
...
cal10 = (a8-a7)*4 +Math.sqrt(a9);
}
And APPROACH2
var nn = myarray.lenght;
for (var x=0;x<nn;x++)
{
// calculations
cal1 = my_array[x].data1*my_array[x].data10 +my_array[x].data2*Math.abs(my_array[x].data3);
...
cal10 = (my_array[x].data8-my_array[x].data7)*4 +Math.sqrt(my_array[x].data9);
}
Assign a1 ... a10 values from my_array and then make calculations is faster than make the calculations using my_array[x].properties; or the right is the opposite ?????
I dont know how works the 'js compiler' ....
The kind of short answer is: it depends on your javascript engine, there is no right and wrong here, only "this has worked in the past" and "this don't seem to speed thing up no more".
<tl;dr> If i would not run a jsperf test, i would go with "Cached example" 1 example down: </tl;dr>
A general rule of thumb is(read: was) that if you are going to use an element in an array more then once, it could be faster to cache it in a local variable, and if you were gonna use a property on an object more then once it should also be cached.
Example:
You have this code:
// Data generation (not discussed here)
function GetLotsOfItems() {
var ret = [];
for (var i = 0; i < 1000; i++) {
ret[i] = { calc1: i * 4, calc2: i * 10, calc3: i / 5 };
}
return ret;
}
// Your calculation loop
var myArray = GetLotsOfItems();
for (var i = 0; i < myArray.length; i++) {
var someResult = myArray[i].calc1 + myArray[i].calc2 + myArray[i].calc3;
}
Depending on your browser (read:this REALLY depends on your browser/its javascript engine) you could make this faster in a number of different ways.
You could for example cache the element being used in the calculation loop
Cached example:
// Your cached calculation loop
var myArray = GetLotsOfItems();
var element;
var arrayLen = myArray.length;
for (var i = 0; i < arrayLen ; i++) {
element = myArray[i];
var someResult = element.calc1 + element.calc2 + element.calc3;
}
You could also take this a step further and run it like this:
var myArray = GetLotsOfItems();
var element;
for (var i = myArray.length; i--;) { // Start at last element, travel backwards to the start
element = myArray[i];
var someResult = element.calc1 + element.calc2 + element.calc3;
}
What you do here is you start at the last element, then you use the condition block to see if i > 0, then AFTER that you lower it by one (allowing the loop to run with i==0 (while --i would run from 1000 -> 1), however in modern code this is usually slower because you will read an array backwards, and reading an array in the correct order usually allow for either run-time or compile-time optimization (which is automatic, mind you, so you don't need to do anything for this work), but depending on your javascript engine this might not be applicable, and the backwards going loop could be faster..
However this will, by my experience, run slower in chrome then the second "kinda-optimized" version (i have not tested this in jsperf, but in an CSP solver i wrote 2 years ago i ended caching array elements, but not properties, and i ran my loops from 0 to length.
You should (in most cases) write your code in a way that makes it easy to read and maintain, caching array elements is in my opinion as easy to read (if not easier) then non-cached elements, and they might be faster (they are, at least, not slower), and they are quicker to write if you use an IDE with autocomplete for javascript :P

Dynamic array using variable

I am trying to dynamically access arrays, otherwise I would have to duplicate my code, when in reality the only difference would be buildablesets1 and buildablesets2.
var buildablesets1 = [""];
var buildablesets2 = [""];
var buildablesetsx = "buildablesets" + playerturn;
for (var i = 0; i < set.length; i++) {
if (doesOwnSet('player'+playerturn, set[i])) {
if(playerturn===1) buildablesets1.push(set[i]);
}
}
if (buildablesetsx.length > 1){
var b = $("#buildsets"+playerturn);
for (k = 1; k < buildablesetsx.length; k++){
b.append("<option value='" + buildablesetsx[k]+ "'>" + buildablesetsx[k] + "</option>");
}
}
The code pushes any sets owned by the player to buildablesetsX. Then the dropdown box #buildsetsX is populated with all the owned sets.
1. How do I get the effect of buildablesetsX where X depends on the players turn.
2. I want to depopulate the dropdown box on each turn otherwise it will duplicate, since it is appending. (would apreciate a better way of doing this, ideally I want to populate the dropdown box only if there is a new set).
Plus sorry I understand that this question has been asked before, but I didnt understand the answers or the question exactly.
Any time you find yourself creating variables with sequential numbers, and wanting to access them dynamically, what you really should be using is an array. In this case, you should use a two-dimensional array:
var buildablesets = [[""], [""]];
The first dimension is the player number, the second dimension is the list of sets that player has built.
To access a particular player's sets, do:
buildablesetsx = buildablesets[playerturn];
The rest of your code will work as you've written it, with the above variable assignment.

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.

Implementing shuffle images function

Im looking for some advice on how to start a shuffle image function, so I have 6 images atm in a div box and I want a function that allows them to shuffle around, how should I start?? Should I put the images in a separate div as well? any help or example code appreciated, thanks
Following is a jQuery solution. You can achieve same results using vanilla JavaScript but it will require few extra lines of code.
<div id="deck">
<div><img src="" /></div>
<div><img src="" /></div>
.
.
.
</div>
// Fisher–Yates Shuffle (Knuth variant)
// To shuffle an array a of n elements (indices 0..n-1):
// for i from n - 1 downto 1 do
// j <- random integer with 0 <= j <= i
// exchange a[j] and a[i]
// jQuery specific:
// 1) remove elements from DOM and convert them into a native JavaScript array
// 2) apply algorithm
// 3) inject the array back to DOM
var a = $("#deck > div").remove().toArray();
for (var i = a.length - 1; i >= 1; i--) {
var j = Math.floor(Math.random() * (i + 1));
var bi = a[i];
var bj = a[j];
a[i] = bj;
a[j] = bi;
}
$("#deck").append(a);
Demo here
I've implemented something like this for a memory card game, so you can probably get some hints from that. Just do a resources search for 'shuffle' in my .js files and you should get an idea how I've done it. From memory, I originally put all my images in a div, then move them around with the shuffle function. I think later on I started shuffling an array of URLs instead then generating the image elements later.
I used Ca-Phun Ung's 'Shuffle' JQuery plugin (although I think I re-wrote my own version to better understand its inner workings). You may find some useful information with that as well. See JQuery Shuffle
Ok, I got this!
var divs = $('selector to get all divs'); // This could be $('img');
function shuffle(divs, iterations) {
var size = divs.size();
for(var i = iterations; i > 0; i--) {
// Pick two divs at random
var div1 = divs[Math.floor(Math.random() * size)],
div2 = divs[Math.floor(Math.random() * size)];
// Ensure they are different divs
if(div1.is(div2)) {
continue;
}
// Swap the two divs
div1.clone().insertAfter(div2);
div2.detach().insertAfter(div1);
div1.detach();
}
};
shuffle(divs, 1000);
Although this will probably be better if you put a divs.hide(), then divs.show() so that you don't see the thrashing. However, maybe that is what you want? May you want a delay in there and use jQuery's animate function to make it fancy. This particular solution requires that the img's position in the DOM determines the location. A more complex solution would be to swap the css position during the loop.
var savedLeft = div1.css("left"),
savedTop = div1.css("top");
div1.css("left", div2.css("left"));
div1.css("top", div2.css("top"));
div2.css("left", savedLeft);
div2.css("top", savedTop);
I haven't actually TRIED this yet, but it looks right from here :P

Categories

Resources