To preface: this is something that I plan to reuse & add to for various web pages. Due to that, I don't want to use any JQuery for this, partially because many pages I use this on won't need anything else (making my use of JQuery sort of like filling a fifty gallon bucket with flour just to use a teaspoon of that flour), & partially because this is designed for mobile browsers with sparse resources.
JQuery's lack of efficiency may not seem like such a big deal, especially since the size of its consolidated library is under 31MB, but according to this page, something as fundamental to JavaScript as its document.getElementById('foo');
call is nearly 16x faster in Chrome than JQueries equivalent: $('#foo');
. Also, despite $('#foo');
being faster in Firefox and Safari than it is in Chrome, document.getElementById('foo');
is still 10x faster in Safari, & an astounding 500x faster in Firefox. Though, if you have a source with contradictary info, feel free to post it in the comments & upvote the comments of any who already did.
I have a set of nav links that I'm trying to move up, at a speed, and to a postion, relative to how far they are from the first nav link. To help give you an idea of what I mean, here's an animation of my nav bar (done with JQuery slideUp & slideDown functions) so you can see how it works (& stutters):
I'll also include the HTML of the navigation bar if that helps:
<nav>
<ul id='nav'>
<li><a href="index.php"><icon><img src="images/home-icon.png"></icon>Home</a></li>
<li><a href="skillsets.php"><icon><img src="images/skills-icon.png"></icon>Skillsets</a></li>
<li><a href="gallery.php"><icon><img src="images/gallery-icon.png"></icon>Gallery</a></li>
<li><a href="about.php"><icon><img src="images/about-icon.png"></icon>About</a></li>
<li><a href="contact.php" style='border-right:1px solid black;'><icon><img src="images/contact-icon.png"></icon>Contact</a></li>
</ul>
</nav>
My JavaScript file has a function called prepareList()
that is just meant to set up the list & collapse it (by moving all but the first nav link up). In order to prevent prepareList()
, & any of the variables it uses, from being initialized before the HTML loads, they're all contained within this function:
document.addEventListener("DOMContentLoaded", function(event) {/*all the code*/});
I will now include my prepareList()
JavaScript function, & the initializations & values of all used variables. I've commented this well enough that you may be able to understand it just from comments in the code, but I will clarify what I want to do afterwards:
//I will consolidate the first three objects into one href variable when I get the program working
var ul = document.getElementsByTagName("nav")[0].children[0],
li = ul.children,
href = new Array(li.length),
is404 = true,
isDown = false;
var itterator;
for(itterator = 0; itterator < li.length; itterator++)
href[itterator] = li[itterator].children[0];
prepareList();
function prepareList() {
//add a plus to the first nav link making it more obvious that it's the button to open the nav
href[0].innerHTML = href[0].innerHTML + "<span> +</span>";
//hide the remaining nav links with animations that move them up, at a speed,
//and to a postion, relative to how far they are from the first nav link
var lastTick = +new Date();
/*This function will be called for each nav link so they move at the same time
navElement is one of the li elements, number is the number (index) of the li element,
and number is negative if the list is collapsing (moving up)*/
var tick = function(navElement, number) {
var navElementStyleTop = parseFloat(navElement.style.top) + number * navElement.clientHeight * (new Date() - lastTick) / 1000;
//navElement.style.top = (navElementStyleTop + number * navElement.clientHeight * (new Date() - lastTick) / 1000) + "px"; //milliseconds
navElement.style.top = navElementStyleTop + "px";
lastTick = +new Date();
//requestAnimationFrame(tick) will only loop tick if the animation can be performed: http://www.javascriptkit.com/javatutors/requestanimationframe.shtml
//setTimeout(tick, 1000/15) will force the animation to be performed if it's been more than 1/15th of a second in order to maintain persistance of motion
if(number < 0){
if (navElementStyleTop > number * navElement.clientHeight)
requestAnimationFrame(tick(navElement, number)) || setTimeout(tick, 1000/15);
else //To prevent rounding errors from messing up the final position
navElement.style.top = (number * navElement.clientHeight) + "px";
}
else{
if (navElementStyleTop < 0)
requestAnimationFrame(tick(navElement, number)) || setTimeout(tick, 1000/15);
else //To prevent rounding errors from messing up the final position
navElement.style.top = "0px";
}
};
/*The for loop is to make this work no matter the number of links in the nav bar
(as the function executed before this may add a link to the nav bar)*/
for(itterator = 1; itterator < li.length; itterator++){
//necessary for top to work
href[itterator].style.position = "relative";
//give top a value so this doesn't rely on the browser defaulting it to 0
href[itterator].style.top = "0px";
tick(href[itterator], (-1) * itterator);
}
}
If you didn't see the issue I can't blame you, it's with requestAnimationFrame(tick(navElement, number))
. In order for this not to complain about recursion, it would have to be executed this way: requestAnimationFrame(tick)
. According to this StackOverFlow answer, requestAnimationFrame
is asynchronous, meaning "the main thread does not wait for the call to requestAnimFrame to return, before" continuing with the function.
The issue is that I have to pass at least number
into the function in order for the function to know which element it is using. The only way I can see to make the function not require any parameters passed in would be for it to be initialized in the for loop every time. I really don't like that solution as I would like to be able to reuse this function so as not to waste cycles (and probably lots of memory) recreating it for each nav link after the first. So, what I'm wondering is if there's a way to pass number
into all of the tick functions while maintaining requestAnimationFrame
's asynchronous nature.
If you have a better way that's far more efficient than JQuery's slideUp & slideDown functions, I'm all ears. Perhaps someone has a far more efficient JavaScript version somewhere online, but I haven't found it.
Feel free to let me know in the comments if you need more information, or if I have any typos.