Reputation: 183
I'm experimenting with adding HTML to the DOM for a small game I'm writing. My design calls for a main-menu system to be brought up first, where-in I'd like to pop in and remove sets of elements.
My question is, what is the most efficient method to do this? After some research, I have implemented elements in the main body with IDs representing the menu section they contain. They look something like this:
<template id="main">
<div class="wrapper_inner">
<main id="main_screen">
<h1>Main menu</h1>
<nav class="vertical">
<button type="button">Characters</button>
<button type="button">Planets</button>
<button type="button">Export...</button>
<button type="button">Settings</button>
</nav>
</main>
</div>
</template>
Then, I call these functions to change menus:
function clearMenu()
{
var content = document.querySelector('.wrapper_inner');
if(content) document.body.removeChild(content);
}
function menuSystem(mode)
{
clearMenu();
document.body.appendChild(document.querySelector('template#'+mode).content.cloneNode(true));
}
UPDATE: The problem is within the
cloneNode()
function. It seemingly creates an inaccessible document-fragment in passing to the body. If someone can find a solution for this, I'll update best answer.
However... This is causing quite a problem in extended testing. I will only be changing menus a few times per session, I'm sure, but I'd like to maintain maximum efficiency. The problem shows up here:
function test()
{
console.log("start test...");
var start = new Date();
for(var i=0; i<10000; i++)
{
menuSystem("main");
}
clearMenu();
console.log("test completed in " + Math.round(((new Date())-start)/1000) + " secs");
}
This outputs around 5-7 seconds the first time, 16 seconds the second time. In Chrome Dev Tools, though I am struggling to understand the output, I can see for each unique "mode" I switch to it adds a documentfragment or two to memory, which is not removed...Also, in the Heap snapshot after running test(), HTMLBodyELement has two HTMLBodyElement objects in it, one with a normal number of elements, and another with thousands of them.
To reiterate: What is the best method to do what I require here? Can my current method be fixed? What's the deal, anyway?
Upvotes: 1
Views: 2771
Reputation: 1074148
Most of the time, you're better off showing and hiding elements rather than adding and removing them. So just put the elements in your markup, then to hide an element:
document.querySelector("some selector").style.display = "none";
and to show it:
document.querySelector("some selector").style.display = "block";
...or "inline"
or "inline-block"
as appropriate.
Or use a class to hide it, and remove the class to show it again:
CSS:
.hidden {
display: none;
}
To hide:
document.querySelector("some selector").classList.add("hidden");
To show:
document.querySelector("some selector").classList.remove("hidden");
IE8 and IE9 (and a couple of niche browser) don't have classList
, but if you look around you can find a shim/polyfill for it.
Side note 1: Your code for adding an element creates an invalid structure, because you don't remove the (The OP is using id
from the cloned element, but then you append it. That means you end up with two elements in the document with the same id
, which is invalid.<template>
elements and appending their content, not them. Look more closely, Teej!)
Side note 2: Your code for removing an menu item could be sped up by remembering the element you found rather than calling document.querySelector
twice. E.g.:
function clearMenu()
{
var wrapperInner = document.querySelector('.wrapper_inner');
if(wrapperInner) {
document.body.removeChild(wrapperInner);
}
}
A synthetic test suggests that doubles the speed of things (which may seem obvious, but wouldn't be the case with the older methods like getElementsByTagName
). Of course, this only matters if you see an actual performance problem, but as that's why you're posting...
Upvotes: 1