Remove a DOM subtree in a fast way - javascript

I have a
<div id='abc'>
in my html page. This div has thousands of children.
Now when I do the following in JavaScript :
var element = document.getElementById("abc");
element.parentNode.removeChild(element);
This takes 12 seconds in IE11.
Any idea on how to make this go faster ?
I just want to destroy an element in a fast manner.
I also tried with setInnerHtml but this takes just as long...

I don't have IE installed on my mac... but I can suggest that you begin with adding a class that hides it, and only then remove it.
Explanation:
Under the constraints of the single-threaded javascript which means that the browser can use only one core for running javascript, modern browsers will run the CSS and all other calculations on different cores. Maybe after doing that, and maybe a little 'setTimeout', the browser will be more 'prepared' for removing the item.
That's only an educated guess.

You trying to do something like this?
document.getElementById('myButton').onclick = function(){
var myElement = document.getElementById('abc');
myElement.remove()
}
<div id='abc'>
<p>1</p>
<p>1</p>
<p>1</p>
<p>1</p>
</div>
<button id='myButton'>teste</button>

This probably wont speed anything up, but it may at least appear to
var frag = document.createDocumentFragment();
var element = document.getElementById("abc");
// hide the div
element.style.display = 'none';
// doing the following in a setTimeout sometimes made a big difference ... sometimes didn't! IE is tempramental
setTimeout(function() {
frag.appendChild(element); // this
element.parentNode.removeChild(element);
}, 100);
I no longer have IE11, Edge isn't much better at this though, and running the same code gave wildly varying results!! So, try this, I hope it helps in some way

Have you tried using innerHTML to overwrite the content instead of deleting each element? I'm not sure if this works, but it doesn't hurt to give it a try. (The original snippet was made by alexqoliveira)
document.getElementById('myButton').onclick = function(){
var myElement = document.getElementById('abc');
myElement.innerHTML = "";
}
document.getElementById('addElements').onclick = function(){
var elements = "";
for (i = 0; i < 10000; i++) {
elements += "<p>1</p>";
}
var myElement = document.getElementById('abc');
myElement.innerHTML = elements;
}
<div id='abc'>
<p>1</p>
<p>1</p>
<p>1</p>
<p>1</p>
</div>
<button id='addElements'>Add 10000 elements</button>
<button id='myButton'>teste</button>

Related

Which is better for performance, elements in DOM or in Javascript object?

In terms of performance, is it better to have 20 elements (with their children) in the DOM tree or everything stored in a Javascript object?
Case 1:
<html>
<body>
<div id='el1'>
<span>...</span>
...
</div>
<div id='el2'>
<p>...</p>
...
</div>
<div id='el3'>
<img src="" alt="" />
<br/>
</div>
<div id='el4'>
<section></section>
...
</div>
...
</body>
</html>
Case 2:
var OBJ = {};
OBJ.el1 = $("<div id='el1'><span>...</span>...</div>");
OBJ.el2 = $("<div id='el1'><p>...</p>...</div>");
OBJ.el3 = $("<div id='el1'><img src="" alt="" /><br/>...</div>");
OBJ.el4 = $("<div id='el1'><section></section>...</div>");
....
My application should only show one of these elements at once. Will it perform better if I just leave them in DOM tree and change their style, or is it preferably to store them in a Javascript object and remove and append them when needed?
How could I test it?
#edit
Extra info to consider:
1- all elements were written in the HTML document and then removed and stored in javascript objects when page was loaded.
2- Everytime the active element changes, I have change the DOM structure by removing the current active element and then appending the new one to it.
#EDIT 2: after some tests
The idea is: someone will write the HTML elements to DOM and then Javascript must show/hide only one of these elements at once. These elements have their own children. It may be a Full Page website or a Web App.
I created 2 test cases:
1- Elements will be removed and then re-appended several times by Javascript. So they will be stored in a Javascript object until it's summoned
2- Elements will be in DOM, I'll hide and show them (jQuery functions, but further tests will change the CSS directly) when needed.
<html>
<head>
<script src='https://code.jquery.com/jquery-1.11.3.min.js'></script>
</head>
<body>
<!-- I USED 15 ELEMENTS IN MY TEST, BUT THE CODE IS TOO LONG -->
<script>
var OBJ1 = {};
var OBJ2 = new Array();
var ACTIVE;
var BODY = $(document.body);
var T1, T2, DELTA;
$(function(){
T1 = new Date().getTime();
/*
//TEST 1: Javascript
var _this,id;
$('div').each(function(){
_this = $(this);
id = _this.attr('id');
OBJ1[id] = _this;
_this.remove();
});
var c;
for (var i = 0; i < 10000; i++) {
for(var k in OBJ1){
c = OBJ1[k];
if(ACTIVE) ACTIVE.remove();
BODY.append(c);
ACTIVE = c;
}
}*/
//TEST 2: DOM
var _this,id;
$('div').each(function(){
_this = $(this);
id = _this.attr('id');
OBJ2.push(id);
});
var c;
for (var i = 0; i < 10000; i++) {
for(var k=0,l=OBJ2.length;k<l;k++){
c = $('#'+OBJ2[k]);
if(ACTIVE) ACTIVE.hide();
c.show();
ACTIVE = c;
}
}
T2 = new Date().getTime();
DELTA = T2 - T1;
console.log('It took '+DELTA+' milliseconds');
});
</script>
</body>
I created 5 generic elements with some Lorem Ipsum and Lorem Pixel content and them copied/pasted them 3 times, making 15 1st-level elements.
For 1000 times I hide and revealed each one of the 15 elements. Ran the test 3 times each.
Case 1 (winner so far): 5.2s, 4.75s, 4.85s
Case 2: 21.5s, 21.5s, 20s
#edit 3: changing Case 2 method
Changing the second case from hide()/show() to css() makes a high increase in performance.
//TEST 2: DOM
var _this,id;
$('div').each(function(){
_this = $(this);
_this.css('display','none');
id = _this.attr('id');
OBJ2.push(id);
});
var c;
for (var i = 0; i < 15000; i++) {
for(var k=0,l=OBJ2.length;k<l;k++){
c = $('#'+OBJ2[k]);
if(ACTIVE) ACTIVE.css('display','none');
c.css('display','block');
ACTIVE = c;
}
}
Also increase the loop to 15k.
Case 1: Around 6.5s
Case 2: Around 2.5s
Definitely leave it in the DOM tree. I would just use show/hide methods to show which one you want.
I would only use the JS method if there was a dynamic changing value that should be reflected in the element, but if they are all static, changing the DOM tree is much more performance heavy rather than just a CSS style change.
You can test this by writing both, then using a jsperf to compare.
Javascript is a very fast language, however the DOM is a huge bottleneck. So having the data in a Javascript object will be much faster, but you will have to render the active element to the DOM on your own (Instead of just making it visible).
I wouldn't worry about the DOM being slow for a few dozen elements, but if you have lots of elements or are trying to perform computations/filters on them, definitely go for Javascript.
I think the only dom elements you might want to apply lazy rendering are those that require media content like image, audio, video,etc. For those, search for a lazy loading js library that load the content of that element when you show that element. Also you could leave the element loaded into the page and hidden, but maybe if there are too many media elements it might be a bad use of memory.
Lastly, you could use React.js for this since it's main job is to dinamically render the diff between two dom trees, this might give some great improvements in scenarios where you are rendering the same dom element but with slightly different content. You probably knew about this, is there any reason why you decided to go full vainilla.js on this ?

Replacing an Element with it's Contents IE8

I am trying to replace some "nested" classes from some HTML with javascript/jquery. I need to get rid of the spans with class="foo" and leave behind only "stuff".
<ul>
<li>
<span class="foo">stuff
<ul>
<li><span class="foo">stuff</span>
</li>
</ul>
</span>
</li>
</ul>
This works in everything but IE8 (which I must unfortunately support):
$(".foo").each(function () {
$(this).replaceWith(this.innerHTML);
});
Before someone points out that there are other similar questions, please note that I've tried several methods outlined in those questions and I think my use case differs because of the "nesting". I've tried the solutions in topics on this site and others. Some of these completely crash IE8, others just don't work.
I am aware of trying to use .empty() before replaceWith, but this doesn't help... my problem isn't performance (yet) it is getting it to even work at all.
I've also tried this:
$(".foo").each(function () {
var HTMLshunt = this.innerHTML;
$(this).after(HTMLshunt).remove();
});
Why the "HTMLshunt" var? I was working on the premise that maybe it wasn't working in IE8 because the "after" wasn't really going "after" .foo, but inside it... because in IE8 something happens with this one: it eliminates every .foo but leaves no contents of foo behind.
This "nesting" isn't helping. I think something else cobbled together along the way would have worked if it weren't for the nesting, but it doesn't matter because there IS this nesting. If anyone can help, please respond.
You could try this, should be IE8 friendly (though I do believe jQuery 1.11.0 should also support IE8). If reflow is any worry then you could cloneNode first at the expense of some memory instead.
HTML
<ul>
<li>
<span class="foo">stuff
<ul>
<li>
<span class="foo">stuff</span>
</li>
</ul>
</span>
</li>
</ul>
Javascript
var fooClasses = document.querySelectorAll('.foo'),
fooIndex,
foo,
fragment;
for (fooIndex = fooClasses.length - 1; fooIndex >= 0; fooIndex -= 1) {
fragment = document.createDocumentFragment();
foo = fooClasses[fooIndex];
while (foo.firstChild) {
fragment.appendChild(foo.firstChild);
}
foo.parentNode.replaceChild(fragment, foo);
}
On jsFiddle
With a cloned node
var fooClasses = document.querySelectorAll('.foo'),
fooIndex,
foo,
fragment,
clone;
for (fooIndex = fooClasses.length - 1; fooIndex >= 0; fooIndex -= 1) {
fragment = document.createDocumentFragment();
foo = fooClasses[fooIndex];
clone = foo.cloneNode(true);
while (clone.firstChild) {
fragment.appendChild(clone.firstChild);
}
foo.parentNode.replaceChild(fragment, foo);
}
On jsFiddle
It seems as though you are using jQuery. In that case, you should just use the unwrap() method:
Description: Remove the parents of the set of matched elements from the DOM, leaving the matched elements in their place.
As an example:
$(".foo").each(function () {
$(this).children().unwrap();
});
jsFiddle demonstration
This fiddle uses jQuery 1.9.1, so it should function in IE8.
EDIT
Okay, so the problem is that jQuery.unwrap doesn't work when a node only contains text content.
In order to work with text content, you'll have to use a slightly different approach:
$(".foo").each(function() {
$(this).replaceWith(this.childNodes);
});
See this fiddle
Full disclosure: I used this answer for the technique.

Fastest way to traverse DOM (Breadth First)

Sorry if this will sound like a strange need.
Users can create a HTML page with any number of elements in any order, Div's will be assigned a class. I have no control over which order or where they are placed.
I need to traverse the DOM and call an init function on each div with the class names specific to my framework. So I do not know the order they will be placed.
Here are my challenges:
- I am using a recursive .children() Jquery call then iterating through each child of the current element... I think I need to find a faster way of doing this, it is a performance hit.
- When iterating, $.each() is faster than a standard for loop. I have no idea why. Does $.each() perform faster for smaller sets? (i.e. size < 20 )
My other alternative is to do a selector on each class but I need to do several iterations as this can't guarantee the ordering so the iterations cancel out any performance advantage.
So My needs:
- A better performing way to traverse the DOM breadth first. Plain ol' Javascript is fine if I can find better performance than JQuery functions.
Sorry, I know this sounds a bit of a strange need. Any advice is greatly welcome.
First of all, you shouldn't be having a huge performance hit. The issue could somewhere else in your code, but you haven't shown us what you currently have, so I have no way of saying. That said, here's an idea that should be pretty speedy:
var classes = ["class1", "class2", "class3"];
i = classes.length, divs, j, curDiv;
while(i--) {
divs = document.getElementsByClassName(classes[i]);
j = divs.length;
while(j--) {
var curDiv = divs[j];
// call some init function on the current div
}
}
If this isn't fast, you must have an issue somewhere else (also, don't forget to make sure this is called after the document has loaded).
As for whether $.each() is faster than a standard for loop, this is pretty much a rule of thumb: native code is faster than library code (but library code is usually more convenient). That holds true in this case; your average for loop will be much faster than $.each().
Here's something a little bit more concise, with some current syntax.
function bfs(root) {
const queue = [];
while (root) {
console.log(root);
[...root.children].forEach(child => queue.push(child));
root = queue.shift();
}
}
bfs(document.body);
<html>
<head>
<title>Breadth First</title>
</head>
<body>
<div class="a">
<div class="aa">
<span class="aaa"></span>
<span class="aab"></span>
</div>
</div>
<div class="ab">
<span class="aba"></span>
<span class="abb"></span>
</div>
<script src="main.js"></script>
</body>
</html>
For breadth first tree traversal...
const html = `<div class="a">
<div class="aa">
<span class="aaa">
</span>
<span class="aab">
</span>
</div>
<div class="ab">
<span class="aba">
</span>
<span class="abb">
</span>
</div
</div>`;
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
function BFT_dom(root) {
const queue = [];
let currentEl = root;
while(currentEl) {
console.log(currentEl) // Do something to element here
for(let i = 0; i < currentEl.children.length; i++) {
queue.push(currentEl.children[i]);
}
currentEl = queue.shift();
}
}
BFT_dom(doc)

How to target and affect dom element in js (noobie)

so long story short,
I'm testing something out, a simple rollover and i notice that for example, if i use this
css:
#box{ width:200px; height:200px; background-color:#F00;}
html:
<div id="box" class="boxClass">this is sample text</div>
Js:
var box = document.getElementById("box");
function changeBox(){
//box.style.backgroundColor = "#1896fa";
box.style.backgroundColor = "#1896fa";
}
box.onmouseover = changeBox;
it works, simple as can be. simple mouseover, simple js...simple. Mouse rolls over and changes from red to bue...good, but then when i try to target the same div via
var box = document.getElementsByTagName("div");
or
getElementsByClassName("boxClass");
nothing happens. my ? is why?
reason im asking is because, i have a simple image gallery and wanted to implement gallery wide rollovers but didnt want to write them for EVERY image.
was trying to somehow group them all together via tagName("img") or ClassName("boxClass")
but to no avail. i know older browsers dont support some of these but i looked it up and saw all modern browsers sans Ie are a go. what am i missing?
any tips help direction is appreciated. thanks in advance.
Contrary to the getElementById function which returns a single element, the getElementsByTagName function returns a NodeList of DOM elements (which is normal as you could have multiple elements with this tag). So if you want to change the background color to all <div> elements in the DOM you will have to first loop through the elements of this collection.
var boxes = document.getElementsByTagName("div");
for (var i = 0; i < boxes.length; i++) {
boxes[i].style.backgroundColor = "#1896fa";
}
Same remark stands true for the getElementsByClassName function.

Javascript: Setting Z-Index on Multiple Elements (ID & Class)

I have a javascript function that is supposed to hide and show layered divs by simply changing the z-index. I don't know if this is the best way to do it, but it works except for one issue. I have the content divs (absolutely positioned in CSS on top of each other), but I also have a navigation div (absolutely positioned in CSS at the bottom of the page) that should always remain on top. So I have this javascript code:
<script type="text/javascript">
var z = 1;
function showHide(id) {
document.getElementById(id).style.zIndex = z++;
document.getElementsByTagName('nav').style.zIndex = z++;
}
</script>
And I have this html:
<div id="1a" class="content" style="z-index:1;">Content</div>
<div id="1b" class="content">More Content</div>
<div id="1c" class="content">Even More Content</div>
<div class="nav" style="z-index:2;">
1
| 2
| 3</div>
The problem is that the z-index line for the navigation div messes it up. Not only does it not execute, but anything I put after it doesn't get executed as well (even a basic alert). If I change navigation from a class to an id, it works fine, but I'm going to have multiple navigation divs on each page (multiple slides in a SlideDeck). I could just set the navigation div's z-index to 99999, but I wanted to see why it wasn't working in the "cleaner" way, since it looks like I might be making a basic mistake.
Thank you.
I'm not sure if this is exactly what you're after, but you need to create a loop for getElementsByTagName or getElementsByClassName:
var cells = table.getElementsByClassName('nav');
for (var i = 0; i < cells.length; i++) {
cells[i].style.zIndex = z++;
}
Edit: Changed method call to getElementsByClassName. I initially just took what he wrote and added the loop.
Looks like your problem is that you are trying to use getElementsByTagName when you should be using getElementsByClassName. getElementsByTagName searches for elements based on tag name, like 'div' or 'span', not class names.
So, use it like this:
<script type="text/javascript">
var z = 1;
function showHide(id) {
document.getElementById(id).style.zIndex = z++;
document.getElementsByClassName('nav')[0].style.zIndex = z++;
}
</script>
Keep in mind that method was added in Firefox 3 and may not be supported in your browser. I would recommend using something like jQuery to maintain cross browser compatibility. Using jQuery, it would look like this:
<script type="text/javascript">
var z = 1;
function showHide(id) {
$('#'+id).style.zIndex = z++;
$('.nav').style.zIndex = z++;
}
</script>

Categories