Ecommerce Developer
 
 

Code

Build a JavaScript Accordion Menu From Scratch

 

JavaScript accordions—so called because of how they "fold up"—are a great way to manage sidebar navigation or even layered menus.

In this tutorial, I will demonstrate how to build a basic JavaScript accordion from scratch, without having to rely on a JavaScript library like jQuery or Prototype.

Dirty Coast: Nice New Orleans Shirts

As a quick bit of background, I decided to write this tutorial after a friend pointed me to the JavaScript accordion on Dirty Coast, an online retailer of "shirts" and "stuff" from New Orleans. The company does a good job of presenting images, product data, and additional content. They also write some very nice code. In fact, we featured Dirty Coast in a showcase right here on Ecommerce Developer.

Image shows Dirty Coast accordion closed

The Dirty Coast JavaScript accordion has two particulars that I want to mention. First, the accordion adjusts to the height of the material being hidden or shown, which is terrific. But second, a blue dotted outline appears around clicked items in the accordion, which, as a matter of preference, may not be so good.

image shows dotted blue line around one of the links on the Dirty Coast accordion

As a result, the accordion I will demonstrate will be adjustable to accommodate differing heights—just like the one on Dirty Coast—but will use some CSS to eliminate any dotted outline like the one that appears on the Dirty Coast example.

Step 1: The HTML

For me, everything starts with the HTML, so here is the basic code for my accordion. It includes a <div> tag with an ID of "accordions," and several child anchor and div elements. I have purposely set the content up so that an anchor is immediately followed by the div containing the content that I want to hide or show when a user interacts with (clicks on) that anchor. This order is important since I am going to be referencing the nextElementSibling in my JavaScript. But more on that below.

You will also want to notice that the <div> tag with an ID of "accord1" contains two paragraph elements, one of which includes an image. I added the image to make sure that this first example had a different height than the other "innerAccordion" divs on the page, since part of my goal is to have the accordion work without specifying an element height.

<!doctype html>
<html lang="en">
<head>
	<title>Accordion Navigation</title>
	<link type="text/css" rel="stylesheet" href="style.css">
	<link rel="icon" type="image/png" href="favicon.png">
</head>
<body>
	<div id="wrapper">	
	<div id="accordions">
		<a href="#">Shop STIKFAS</a>
		<div id="accord1" class="innerAccordion"><p>one</p> <p><img src="stikfas.png" alt="shop STIKFAS" /></p></div>
		
		<a href="#">Shop TANKS</a>
		<div id="accord2" class="innerAccordion"><p>two</p></div>
		
		<a href="#">Shop PEACE</a>
		<div id="accord3" class="innerAccordion"><p>three</p></div>
		
		<a href="#">Shop HANDGUNS</a>
		<div id="accord4" class="innerAccordion"><p>four</p></div>	
	
	</div><!--end accordions-->
	</div><!--end wrapper-->
	<script src="js.js"></script>
</body>
</html>

Step 2: Write the CSS

I start by centering the content, resetting images, and adding some font data, which simply makes my accordion easier to work on.

body {background: #fff; margin: 0; padding: 0; font-family: corbel;}
img {margin: 0; padding: 0; border: none;}
#wrapper {margin: 25px auto; width: 350px;}

Next, I add some more specific styles for the accordion section.

#accordions a {display: block; width: 100%; padding: 1em 0 0; border: none; }
#accordions div.innerAccordion {display: block; position: relative; overflow: hidden;}
#accordions div.innerAccordion  p {margin: 0; padding: 0;}

Both the anchor and <div> tags are set to display as blocks. And the <div> tags will hide any overflow. I also reset the margin and padding for the nested paragraph tags because I want my content to fit right up against my anchors.

I also want to point out that by setting the "innerAccordion" divs to display:block rather than display:none, I am making a development choice. If for any reason, a user does not have a JavaScript-capable browser or is, perhaps, using a screen reader, I want this content to be included. So by default, all of the content is open and displayed as shown below.

Show tutorial accordion with all divs open

Step 3: Hide the Content

Now that I have all of the content on page and showing, I need to hide the "innerAccordion" divs.

function hideDiv()
{
	var divs = document.getElementsByTagName('div');
	var divsL = divs.length;
	for(var i = 0; i< divsL; i++)
	{
		if(divs[i].className == "innerAccordion")
		{
			divs[i].style.display = "none";
		}
	}
}

hideDiv();

As you can see, this function gets all of the <div> tags on the entire page and sorts through each looking for the "innerAccordion" class name. When it locates one of the "innerAccordion" <div> tags, it changes the style display setting to "none," effectively hiding the content.

shows tutorial accordion with divs hidden

Step 4: Show the Content When a User Clicks

Now, I want to display the "hidden" divs when a user clicks on the corresponding anchor tag. First, I listen for a mouse click event.


document.onclick = accordIt;

Next, I write the function my event listener will call.


function accordIt(e)
{
	if (!e) var e = window.event;
	var target = e.target || e.srcElement;
	
		var nS = target.nextElementSibling || target.nextSibling.nextSibling
		if(nS.style.display == "none")
		{
			nS.style.display = "block";		
			var divH = nS.clientHeight;
			nS.style.height = "1px";
			slideDown(nS, divH);		
		}
		else if(nS.style.display == "block")
		{
			var divH = nS.clientHeight;
			slideUp(nS, divH);
		}

}

As you may know, the first part of this function simply compensates for differences in how different browsers handle JavaScript events.

if (!e) var e = window.event;
var target = e.target || e.srcElement;

The next section of the function identifies the nextElementSibling. I am, again, compensating for differences in Microsoft's Internet Explorer (IE).

var nS = target.nextElementSibling || target.nextSibling.nextSibling

After identifying the nextElementSibling, which will be our "innerAccordion" div, the function (1) changes the div's display to "block;" (2) determines the div's height; (3) resets the div's height to "1px;" (4) initiates another function named slideDown.

if(nS.style.display == "none")
		{
			nS.style.display = "block";		
			var divH = nS.clientHeight;
			nS.style.height = "1px";
			slideDown(nS, divH);		
		}

The slideDown function receives two parameters. First there's nS, which represents the div, and, second, divH which represents the original div height. The function then checks to learn if the div's current height is less than its original height. This check is important because I programmatically set the div height to "1px" above and I now want to incrementally increase that height in order to have it slide into view. If the div's current height is less than its original height, the function adds one pixel to the element's current height and sets a timer that will restart the function in just 10 milliseconds.

function slideDown(ele, hei)
{
	if(parseFloat(ele.style.height) < hei)
	{
		var h = parseFloat(ele.style.height);
		ele.style.height = (h + 1) + "px";
		downTimer = setTimeout(function(){slideDown(ele, hei)}, 10);
	}
	else{clearTimeout(downTimer);}
	
	
}

You may want to take note of how I passed the parameters through the timer.

downTimer = setTimeout(function(){slideDown(ele, hei)}, 10);

Sending parameters via a timer is no mean feat. IE and other browsers will normally produce an error if you do it in what seems like the most reasonable way (shown below).

downTimer = setTimeout("slideDown(ele, hei)", 10)

Rather, for it to work across browsers, you need to write it as I have done. function(){somefunction(parameter, parameter)}, time in millisecond).

Once the current height of the div is the same as the original height, the timer is cleared. Don't miss this important step, if you just let the timer keep running, your accordion will only open and close once, since after that two conflicting function timers will be working against each other.

else{clearTimeout(downTimer);}

Shows tutorial accordion with one div open

Step 5: Hide the Content When a User Clicks

At the moment, my accordion opens but does not close. I fix that problem by going back to the function accordIt. Where an "else" statement initiates the function, slideUp if the div's display is currently set to "block."

else if(nS.style.display == "block")
		{
			var divH = nS.clientHeight;
			slideUp(nS, divH);
		}

Notice that I am again capturing the original or open height for the div. This is important since I will need to reset the height attribute once the div is hidden. If I did not reset the height attribute, it would remain at zero, and would not open the second time a user clicked on the associated anchor.

The slideUp function first tests the div's current height to learn if it is greater than zero. If this test returns true, the function subtracts one pixel from the div's height and sets a timer to restart the function in 10 milliseconds. If the div's current height is equal to or less than zero, the function (1) resets the div's display attribute to "none;" (2) resets the div's height to its original height; and (3) clears the timer.

function slideUp(ele, hei)
{
	if(parseFloat(ele.style.height) > 0)
	{
		var h = parseFloat(ele.style.height);
		ele.style.height = (h - 1) + "px";
		upTimer = setTimeout(function(){slideUp(ele, hei)}, 10);
	}
	else{
		ele.style.display = "none";
		ele.style.height = hei + "px";
		clearTimeout(upTimer);
	}
	
	
}

Step 6: Get Rid of the Outline

By default, most browsers put an outline around anchor elements when they are clicked. Most users never notice, since a click usually takes you to a different page. But in our accordion, that outline hangs around, just like it did in the Dirty Coast example.

shows dotted outline around a link on the tutorial accordion

To get rid of the outline, just add outline: 0; to the style description for the anchor tag.

#accordions a {display: block; width: 100%; padding: 1em 0 0; border: none; outline: 0;}

Now the outline is gone.

shows tutorial accordion without dotted outline

Summing Up

That's it. I now have a functioning JavaScript accordion that smoothly sides up and down when a user clicks.

Related Articles

1 Comment

Rss-sm