Javascript animated collapsible panels without any frameworks

Share on facebook
Share on twitter
Share on linkedin
Share on reddit

If you need to make collapsible panels that remember their state, but don’t want to use any javascript framework, this tutorial will help you do this with no headaches – and you’ll learn some neat javascript tricks too!

Javascript animated panels
Javascript animated panels

In case you’re wondering why no frameworks, the reason is two-fold.

  • First, although frameworks are nice, they’re BIG. jQuery weighs over 50Kb; it’s not much if you’re building an entire web app, but if you’re only looking for some specific effect, it’s overkill considering that you can achieve the same thing in less that 3Kb. (Note: To add insult to injury, if you are using a CMS or blog with plugins, they sometimes use different frameworks, so the page ends up loading jQuery, MooTools and Scriptaculous to display some simple effects.)
  • Second, by doing it yourself, you have the potential to actually learn more about javascript (only if you want to — the code I’m presenting is pretty much plug-and-play) and reuse the concepts to do other things – like a collapsible tree menu for example.

You can see an online demo.
UPDATE 13 May 2009 – I have updated the code slightly, it’s now possible to start the panels in collapsed state by default.

Features at a glance

  • works with any number of panels in a page;
  • plug-and-play – can be used with little or no tweaks;
  • shows different arrows and style for the expanded/collapsed state;
  • animated transitions;
  • each panel’s state is saved and remembered between sessions;
  • tested in IE6, IE7, IE8, Firefox 3, Chrome, Safari 4 and Opera 9.

Table of Contents:

Start with layout and style

For this tutorial we’ll use a minimal style; since the article is not about CSS, I will only briefly cover this.

HTML

The required HTML for a panel is very simple:

One panel

… content goes here …

We have a DIV that encloses the title (in our case a H2 tag, but you could use something else) and another DIV that will hold the actual content. We need this kind of nested structure, but if anyone thinks of a more semantic approach, it can be changed.

CSS

For our panel we will use two main classes, .panel (for expanded state) and .panelcollapsed (for the collapsed state), like this:

  1. .panel, .panelcollapsed
  2. {
  3. 	background: #eee;
  4. 	margin: 5px;
  5. 	padding: 0px 0px 5px;
  6. 	width: 300px;
  7. 	border: 1px solid #999;
  8. 	-moz-border-radius: 4px;
  9. 	-webkit-border-radius: 4px;
  10. }

Feel free to change the layout as you see fit. Note that in the example above Firefox and Webkit-based browsers (Chrome and Safari) will also get some nice rounded corners.

For headings I’m using an arrow icon on the background, the rest of the style is pretty plain:

  1. .panel h2, .panelcollapsed h2
  2. {
  3. 	font-size: 18px;
  4. 	font-weight: normal;
  5. 	margin: 0px;
  6. 	padding: 4px;
  7. 	background: #CCC url(arrow-up.gif) no-repeat 280px;
  8. 	border-bottom: 1px solid #999;
  9. 	-moz-border-radius: 3px;
  10. 	-webkit-border-radius: 3px;
  11. 	border-top: 1px solid #FFF;
  12. 	border-right: 1px solid #FFF;
  13. 	border-left: 1px solid #FFF;
  14. }

For a collapsed panel, we want to change the style slightly and change the arrow direction:

  1. .panelcollapsed h2
  2. {
  3. 	background: #CCC url(arrow-dn.gif) no-repeat 280px;
  4. 	border-color: #CCC;
  5. }

I also wanted to add a little ‘rollover’ effect on the heading (not visible in IE6):

  1. .panel h2:hover, .panelcollapsed h2:hover { background-color: #A9BCEF; }

Finally, we’ll add just a little style for the panel content:

  1. .panelcontent
  2. {
  3. 	background: #EEE;
  4. 	overflow: hidden;
  5. }
  6.  
  7. .panelcollapsed .panelcontent { display: none; }

Adding the interactivity

Because I wanted the html to be as simple as possible and to work with any number of panels, I did not add links with onclick handlers (e.g. ) and I also didn’t want to have to add IDs to panels. Therefore, I made the script ‘smart’ enough to discover the panels and add the onclick handlers by itself. This is not as hard as it may seem:

  1. var PANEL_NORMAL_CLASS    = "panel";
  2. var PANEL_COLLAPSED_CLASS = "panelcollapsed";
  3. var PANEL_HEADING_TAG     = "h2";
  4.  
  5. function setUpPanels()
  6. {
  7. 	var headingTags = document.getElementsByTagName(PANEL_HEADING_TAG);
  8.  
  9. 	for (var i=0; i < headingTags.length; i++)
  10. 	{
  11. 		var el = headingTags[i];
  12. 		if (el.parentNode.className != PANEL_NORMAL_CLASS && el.parentNode.className != PANEL_COLLAPSED_CLASS)
  13. 			continue;
  14. 		el.onclick = function()
  15. 		{
  16. 			var target    = this.parentNode;
  17. 			var collapsed = target.className == PANEL_COLLAPSED_CLASS;
  18. 			target.parentNode.className = collapsed ?  PANEL_NORMAL_CLASS : PANEL_COLLAPSED_CLASS;
  19. 		};
  20. 	}
  21. }
  22.  
  23. // Register setUpPanels to be executed on load
  24. if (window.addEventListener)
  25. 	window.addEventListener("load", setUpPanels, false);
  26. else
  27. if (window.attachEvent)
  28. 	window.attachEvent("onload", setUpPanels);

First, I’m setting up some constants, just in case you’ll want to use different tags for the heading or different class names.The setUpPanels function works like this:

  1. using getElementsByTagName we get all H2 headings in the page and collect them into an array;
  2. we go through each collected element:
    1. test to see if it’s inside a panel by accessing its parent class name, skip if it’s not panel or panelcollapsed.
    2. add an onclick handler for the heading. This will work fine even without a link, although the hover effect will not work in IE6. The function does the following:
      1. get the heading’s parent, that’s the panel itself.
      2. get its class name, switch between panel and panelcollapsed.

Finally, we need a way to make the setUpPanels to execute upon window load. Now, instead of using a construct, we’ll use something more clever. Unfortunately, as always IE plays different, so we’ll have to use two methods: the correct DOM method via addEventListener and the IE way with attachEvent.

Making changes persistent

To be really useful for the user, the expanded/collapsed preferences should be saved in a cookie, so that the next time the page is loaded, the panels are already expanded or collapsed. For this, first we’ll use an object called panelsStatus that will keep the expanded/collapsed status for each panel; it will act as an associative array or a hash. The ‘key’ will be the panel’s name (the text in the H2 tag) and the value will be “true” for an expanded panel and “false” for a collapsed one.

To keep code neat, we’ll also define our cookie name with

  1. var PANEL_COOKIE_NAME = "panels";

Saving settings

Saving will take place each time a panel is toggled.

  1. function saveSettings(key, value)
  2. {
  3. 	panelsStatus[key] = value;
  4.  
  5. 	var panelsData = [];
  6. 	for (var key in panelsStatus)
  7. 		panelsData.push(key+":"+panelsStatus[key]);
  8.  
  9. 	var today = new Date();
  10. 	var expirationDate = new Date(today.getTime() + 365 * 1000 * 60 * 60 * 24);
  11.  
  12. 	document.cookie = PANEL_COOKIE_NAME + "=" + escape(panelsData.join("|")) + ";expires=" + expirationDate.toGMTString();
  13. }

Step by step, here’s what the function does:

  1. get the modified key-value pair and put it in the panelsStatus object;
  2. go through all elements in the object and combine entries in a string like this: “key:value“, put in a temporary array;
  3. get today date and add one year to it (that will be the cookie expiration date);
  4. join the temporary array elements into one string like “key1:value1|key2:value2|key3:value3” and write in the cookie.The cookie string will look like this: panels=One%20panel%3Afalse;expires=Sun, 11 Apr 2010 09:40:22 GMT

For simplicity I’ve used “:” (colon) and “|” (pipe) as separators. If you think you’re likely to encounter those in the panel name, you can replace them with any weird character you can think of.

Loading settings

Loading will be done once, when the page is loaded.

  1. function loadSettings()
  2. {
  3. 	panelsStatus = {};
  4.  
  5. 	var start = document.cookie.indexOf(PANEL_COOKIE_NAME + "=");
  6. 	if (start == -1) return;
  7. 	start += PANEL_COOKIE_NAME.length+1;
  8. 	var end = document.cookie.indexOf(";", start);
  9. 	if (end == -1) end = document.cookie.length;
  10.  
  11. 	var cookieValue = unescape(document.cookie.substring(start, end));
  12. 	var panelsData = cookieValue.split("|");
  13.  
  14. 	for (var i=0; i< panelsData.length; i++)
  15. 	{
  16. 		var pair = panelsData[i].split(":");
  17. 		panelsStatus[pair[0]] = pair[1];
  18. 	}
  19. }

Here’s what the code does:

  1. create the panelsStatus object;
  2. find the cookie name in the document.cookie string, return if not found;
  3. find the end of the cookie value string (before the “;expires=“);
  4. take the cookie value and split by by the “|” character, put in a temporary array;
  5. go through each element in the array, split by “:” and save in the panelsStatus object as key/value pair.

Animating the transition

For some reason, people think animation is difficult in browser. It’s not. Think of it this way: any property that you can set via CSS, you can alter dynamically in javascript. To animate, you just need to call a function repeatedly, and this is most easily achieved with setTimeout or setInterval().

Preparing the transition

First, let’s define some new constants:

  1. var PANEL_CONTENT_CLASS   = "panelcontent";
  2. var PANEL_ANIMATION_DELAY = 20; /*ms*/
  3. var PANEL_ANIMATION_STEPS = 10;

So, we have defined the class name for the panel content, the delay in milliseconds between two calls to the animation function and the total number of steps to use for the animation.

To keep things as flexible as possible, I wanted the script to work easily regardless of the panel height, so when animating, we’ll dynamically change the height of the panel content (that’s why it needs the overflow:hidden definition in CSS). If you want all panels to have the same height, maybe with scrollable content, I suggest adding another DIV in the panel content and setting its height and overflow.

  1. function animateTogglePanel(panel, expanding)
  2. {
  3. 	var elements = panel.getElementsByTagName("div");
  4. 	var panelContent = null;
  5. 	for (var i=0; i < elements.length; i++)
  6. 	{
  7. 		if (elements[i].className == PANEL_CONTENT_CLASS)
  8. 		{
  9. 			panelContent = elements[i];
  10. 			break;
  11. 		}
  12. 	}
  13.  
  14. 	panelContent.style.display = "block";
  15. 	var contentHeight = panelContent.offsetHeight;
  16.  
  17. 	if (expanding)
  18. 		panelContent.style.height = "0px";
  19.  
  20. 	var stepHeight = contentHeight / PANEL_ANIMATION_STEPS;
  21. 	var direction = (!expanding ? -1 : 1);
  22.  
  23. 	setTimeout(function(){animateStep(panelContent,1,stepHeight,direction)}, PANEL_ANIMATION_DELAY);
  24. }

This function expects two parameters – a reference to the panel that needs to be animated and whether it should be an expanding or collapsing animation.

  1. First, it finds the DIV that has the panelcontent class by collecting all descendant DIVs and going through them until it finds the one that has the right class name. This process could have been simplified a little by using getElementsByClassName, but that function is not supported by IE (surprise, surprise).
  2. Then, we need to get the height of the panel content, so we make sure it’s displayed by setting its display propery to “block” and read its height with offsetHeight property. offsetHeight return its total height, meaning defined height + paddings + border widths, so you should not define any paddings or borders in the panelcontent class, or you’ll get inaccurate results.
  3. if the animation is for expanding, we need the content visible, but it has to start with a height of 0.
  4. calculate by how much the panel should expand or contract on each step by dividing the total height by the number of steps, and the direction – positive for expansion, negative for contraction
  5. set the timeout – here’s a tricky thing: we can’t call animateStep function directly because we need to pass the reference to the panel content to it. So we set an anonymous function, which in turn will call animateStep. It’s confusing, but it works – for more details, read about setInterval callback arguments on Mozilla Developer.

The next function is called every 20 miliseconds. It receives a reference to the panel content, the current step (iteration number), the step height and direction.

  1. function animateStep(panelContent, iteration, stepHeight, direction)
  2. {
  3. 	if (iteration < PANEL_ANIMATION_STEPS)
  4. 	{
  5. 		panelContent.style.height = Math.round(((direction > 0) ? iteration : PANEL_ANIMATION_STEPS - iteration) * stepHeight) +"px";
  6. 		iteration++;
  7. 		setTimeout(function(){animateStep(panelContent,iteration,stepHeight,direction)}, PANEL_ANIMATION_DELAY);
  8. 	}
  9. 	else
  10. 	{
  11. 		panelContent.parentNode.className = (direction < 0) ? PANEL_COLLAPSED_CLASS : PANEL_NORMAL_CLASS;
  12. 		panelContent.style.display = panelContent.style.height = "";
  13. 	}
  14. }

What it does:

  1. checks if the current iteration is smaller than the total number of iterations;
  2. if it is:
    1. change the panel content height to be equal to iteration number multiplied by step height; reverse if it’s a collapsing animation;for example, consider iteration 2 with a step height of 5px, expanding. Panel content height becomes 2*5=10px; at iteration 3 it will be 3*5=15px and so on;for a collapsing animation, if the total number of steps is 10, at iteration 2 we’d have (10-2)*5 =40px, iteration 3 is (10-3)*5=35px.
    2. set the timeout to call animateStep function again after the specified delay.
  3. if it’s not:
    1. switch the class name for the panel (panel content’s parent).
    2. clear any inline styles we’ve set.

Putting it all together

That’s it really. All we need to do now is to integrate the load/save and animation back into our first code.

The final javascript will look like this:

  1. var PANEL_NORMAL_CLASS    = "panel";
  2. var PANEL_COLLAPSED_CLASS = "panelcollapsed";
  3. var PANEL_HEADING_TAG     = "h2";
  4. var PANEL_CONTENT_CLASS   = "panelcontent";
  5. var PANEL_COOKIE_NAME     = "panels";
  6. var PANEL_ANIMATION_DELAY = 20; /*ms*/
  7. var PANEL_ANIMATION_STEPS = 10;
  8.  
  9. function setUpPanels()
  10. {
  11. 	loadSettings();
  12.  
  13. 	// get all headings
  14. 	var headingTags = document.getElementsByTagName(PANEL_HEADING_TAG);
  15.  
  16. 	// go through all tags
  17. 	for (var i=0; i < headingTags.length; i++)
  18. 	{
  19. 		var el = headingTags[i];
  20.  
  21. 		// make sure it's the heading inside a panel
  22. 		if (el.parentNode.className != PANEL_NORMAL_CLASS && el.parentNode.className != PANEL_COLLAPSED_CLASS)
  23. 			continue;
  24.  
  25. 		// get the text value of the tag
  26. 		var name = el.firstChild.nodeValue;
  27.  
  28. 		// look for the name in loaded settings, apply the normal/collapsed class
  29. 		// if not found in cookie leave default - collapsed or not
  30. 		if (panelsStatus[name] == "false")
  31. 			el.parentNode.className = PANEL_COLLAPSED_CLASS;
  32. 		else
  33. 		if (panelsStatus[name] == "true")
  34. 			el.parentNode.className = PANEL_NORMAL_CLASS;
  35. 		else
  36. 			panelsStatus[name] = (el.parentNode.className == PANEL_NORMAL_CLASS) ? "true" : "false";
  37.  
  38. 		// add the click behavor to headings
  39. 		el.onclick = function()
  40. 		{
  41. 			var target    = this.parentNode;
  42. 			var name      = this.firstChild.nodeValue;
  43. 			var collapsed = target.className == PANEL_COLLAPSED_CLASS;
  44. 			saveSettings(name, collapsed?"true":"false");
  45. 			animateTogglePanel(target, collapsed);
  46. 		};
  47. 	}
  48. }
  49.  
  50. /**
  51.  * Start the expand/collapse animation of the panel
  52.  * @param panel reference to the panel div
  53.  */
  54. function animateTogglePanel(panel, expanding)
  55. {
  56. 	// find the .panelcontent div
  57. 	var elements = panel.getElementsByTagName("div");
  58. 	var panelContent = null;
  59. 	for (var i=0; i < elements.length; i++)
  60. 	{
  61. 		if (elements[i].className == PANEL_CONTENT_CLASS)
  62. 		{
  63. 			panelContent = elements[i];
  64. 			break;
  65. 		}
  66. 	}
  67.  
  68. 	// make sure the content is visible before getting its height
  69. 	panelContent.style.display = "block";
  70.  
  71. 	// get the height of the content
  72. 	var contentHeight = panelContent.offsetHeight;
  73.  
  74. 	// if panel is collapsed and expanding, we must start with 0 height
  75. 	if (expanding)
  76. 		panelContent.style.height = "0px";
  77.  
  78. 	var stepHeight = contentHeight / PANEL_ANIMATION_STEPS;
  79. 	var direction = (!expanding ? -1 : 1);
  80.  
  81. 	setTimeout(function(){animateStep(panelContent,1,stepHeight,direction)}, PANEL_ANIMATION_DELAY);
  82. }
  83.  
  84. /**
  85.  * Change the height of the target
  86.  * @param panelContent	reference to the panel content to change height
  87.  * @param iteration		current iteration; animation will be stopped when iteration reaches PANEL_ANIMATION_STEPS
  88.  * @param stepHeight	height increment to be added/substracted in one step
  89.  * @param direction		1 for expanding, -1 for collapsing
  90.  */
  91. function animateStep(panelContent, iteration, stepHeight, direction)
  92. {
  93. 	if (iteration < PANEL_ANIMATION_STEPS)
  94. 	{
  95. 		panelContent.style.height = Math.round(((direction > 0) ? iteration : PANEL_ANIMATION_STEPS - iteration) * stepHeight) +"px";
  96. 		iteration++;
  97. 		setTimeout(function(){animateStep(panelContent,iteration,stepHeight,direction)}, PANEL_ANIMATION_DELAY);
  98. 	}
  99. 	else
  100. 	{
  101. 		// set class for the panel
  102. 		panelContent.parentNode.className = (direction < 0) ? PANEL_COLLAPSED_CLASS : PANEL_NORMAL_CLASS;
  103. 		// clear inline styles
  104. 		panelContent.style.display = panelContent.style.height = "";
  105. 	}
  106. }
  107.  
  108. // -----------------------------------------------------------------------------------------------
  109. // Load-Save
  110. // -----------------------------------------------------------------------------------------------
  111. /**
  112.  * Reads the "panels" cookie if exists, expects data formatted as key:value|key:value... puts in panelsStatus object
  113.  */
  114. function loadSettings()
  115. {
  116. 	// prepare the object that will keep the panel statuses
  117. 	panelsStatus = {};
  118.  
  119. 	// find the cookie name
  120. 	var start = document.cookie.indexOf(PANEL_COOKIE_NAME + "=");
  121. 	if (start == -1) return;
  122.  
  123. 	// starting point of the value
  124. 	start += PANEL_COOKIE_NAME.length+1;
  125.  
  126. 	// find end point of the value
  127. 	var end = document.cookie.indexOf(";", start);
  128. 	if (end == -1) end = document.cookie.length;
  129.  
  130. 	// get the value, split into key:value pairs
  131. 	var cookieValue = unescape(document.cookie.substring(start, end));
  132. 	var panelsData = cookieValue.split("|");
  133.  
  134. 	// split each key:value pair and put in object
  135. 	for (var i=0; i< panelsData.length; i++)
  136. 	{
  137. 		var pair = panelsData[i].split(":");
  138. 		panelsStatus[pair[0]] = pair[1];
  139. 	}
  140. }
  141.  
  142. /**
  143.  * Takes data from the panelsStatus object, formats as key:value|key:value... and puts in cookie valid for 365 days
  144.  * @param key	key name to save
  145.  * @paeam value	key value
  146.  */
  147. function saveSettings(key, value)
  148. {
  149. 	// put the new value in the object
  150. 	panelsStatus[key] = value;
  151.  
  152. 	// create an array that will keep the key:value pairs
  153. 	var panelsData = [];
  154. 	for (var key in panelsStatus)
  155. 		panelsData.push(key+":"+panelsStatus[key]);
  156.  
  157. 	// set the cookie expiration date 1 year from now
  158. 	var today = new Date();
  159. 	var expirationDate = new Date(today.getTime() + 365 * 1000 * 60 * 60 * 24);
  160. 	// write the cookie
  161. 	document.cookie = PANEL_COOKIE_NAME + "=" + escape(panelsData.join("|")) + ";expires=" + expirationDate.toGMTString();
  162. }
  163.  
  164. // -----------------------------------------------------------------------------------------------
  165. // Register setUpPanels to be executed on load
  166. if (window.addEventListener)
  167. {
  168. 	// the "proper" way
  169. 	window.addEventListener("load", setUpPanels, false);
  170. }
  171. else
  172. if (window.attachEvent)
  173. {
  174. 	// the IE way
  175. 	window.attachEvent("onload", setUpPanels);
  176. }

As you can see, the code is fully commented, so any details I’ve left out you should understand easily.

Updates

21 May 2010: Added the option to Expand/Collapse all panels.

Download the code

You can download a fully working example below:  Download Javascript Collapsible Panels

Armand Niculescu

Armand Niculescu

As the Senior Project manager, Armand is one of the rare kind of developers that can do both design and programming with equal skill. This, coupled with a solid background and many years of experience, enables him to see the big picture and plan for the small details.

36 Responses

  1. Thanks Armand, this was a great tutorial.

    I’ve been looking to implement something like this myself for a while now (as I use toggle slide functionality a lot and was always annoyed at having to resort to using a huge Js Library for just that one piece of functionality).

    As an added bonus you threw in the ‘remembering panel state’ logic as well which was great!

  2. Amand, Great article! Exactly what i was looking for.
    One question though, how do i set the default state to ‘colapsed’? Trying to figure it out…

  3. ive tried this on one of my sites and it works kind of the problem is it remembers when a div is closed but not if you then re-open it it just always stays closed until the cookie is deleted

    1. heya armand sorry i never checked back it was happening on firefox 3 and IE on vista, its working great now though after your update, thanks for the great script/tutorial im sure it will help lots of people out :)

  4. Hi Amand very nice and clean solution!

    I realized some strange behavior – when i open the panels in firefox they are colapsed – in internet-explorer they are closed may you have some suggestions … regards Daniel.

  5. Hello,
    I carried out the modification () so that limp them are closed at the beginning. but the last one remains open to launching.
    What can I make? Firefox 3 and IE8 idem
    regards Gérard

  6. Thanks Armand, the panel is really great. I have just tested on firefox 3.0 and IE 7.0 , it works properly.

  7. just i want to ask you what should i change in your code in order to get a collapsible panels in horizontal direction instead of vertical direction.thanks a lot.

    1. There would be some work involved… if you know javascript and html/css you can do it if you follow the logic – edit the javascript and replace height with width and and redo the css. It’s not incredibly hard but you must know your way.

  8. Thanks a lot but i have asked you only after i have tried this trivial ligic but i didn’t got a good result. Any way thank you for your answer.

  9. Please i want to ask you with what should i replace the tag h2 in a way that it will be vertical ( so it will no longer be a title tag).That is my problem when editing your code in order to get vertical cllapsible panels.thanks again.

    1. The HTML can remain the same, but you need to restyle with CSS, basically you need to make the H2 a fixed width and height and make it float left. Also, most likely you’ll have to place an image on the background of the H2 since you can’t rotate the text.
      I will contact you by email with an example.

  10. Hi Armand
    I think your Solution for javascript/css panels is BRILLIANT !
    I have implemented this myself(i am not a programmer nor developer) and I managed to alter the css to get the look and feel I wanted.
    One question though…it works Brilliantly in Firefox(of course) IE 7 & 8 but when I open it in IE6 it doesnt work properly. You have to click the text for the panel to open. When you click the arrow it does not open.
    A small thing I know but is there a way to reslove this as some people still use IE6 (i dont know why though)
    Also, Thank you very much for putting this script out there for us to enjoy !

  11. Armand – Great tutorial! Mad Props.
    I am trying to implement this on a navigation menu. I have modified the HTML & CSS to add links and style. I have 3 panel divs. For all practical purposes one panel now looks like:
    <div class=”panelcollapsed”>
    <h2><a>Projects</a></h2>
    <div class=”panelcontent”>
    <h2><a href=”link1.html”>link1</a></h2>
    <h2><a href=”link2.html#anch”>link2</a></h2>
    <h2><a href=”link3.html”>link3</a></h2>
    </div>
    </div>
    Here is the issue (I think… I am not an experienced programmer):
    With no cookie (fresh cache as well), all panels are collapsed (as they should be). After opening the panel “Projects” and clicking on “link1,” the next page loads, however all the panels are open.
    Basically I think functionality relating to the Cookie that is created is not working properly (either not being saved properly, or the cookie is not being applied properly with a new page load)
    Questions:
    1.) Is it o.k. to have a H2 within a “panelcontent” div? (or should I use ‘p’ tags, like the original HTML?)
    2.) Are the links in the “panelcontent” div sending users away from the page before the cookie can be updated? (could this be fixed by adding a delay to the link somehow?)
    3.) Is there an easy way to modify the JavaScript to skip “everything cookie?” (i.e. have JS not make, save, or use this cookie. Instead each panel starts with the class specified in the HTML document.)
    Thanks again for this great tutorial!

    1. 1. yes, you can put anything inside a panel, just style it with CSS
      2. in my example, the cookie is updated on clicking the panel title, the links inside don’t matter.
      3. yes, it should work if you remove references to loadSettings(), saveSettings() and the code at lie 30 in the original example:
      if (panelsStatus[name] == “false”)
      el.parentNode.className = PANEL_COLLAPSED_CLASS;
      else
      if (panelsStatus[name] == “true”)
      el.parentNode.className = PANEL_NORMAL_CLASS;

      NOTE: I wrote the article in an attempt to help people learn how javascript animation is done; it was not intended a complete, plug-and-play ‘product’.

  12. I’m curious how you would get an “active” h2 tag.
    Basically, when the user expands a panel, I want the h2 tag to remain “highlighted/active”.  Could use the same CSS as the h2:hover class, but not sure how to alter the javascript to append an “active” style when expanded.

  13. I love the comment above that states ‘Brilliant’. Excellent tutorial, and very condensed code. I have been using jquery for years. Its time for a change. I had the same problem(image not clickable) as Shaun with IE6, and found a workaround for it, but I no longer have IE6, so I can not test it with your code. Again, excellent code!! There are a lot of collapsible codes out there, but your formatting (how it looks to the visitor) is what I think is the most important, and yours is very clean, yet professional in appearance.

  14. Armand … great app!
    However, when using it in IE7, the first panel in a series shows a large (700px?) gap between the introductory content <p> and the <div class=”panelcollapsed”>panel.
    my HTML:   
    <h1>…</h1>
    <p>…</p>
        <div class=”panelcollapsed”>
    <h2>…</h2>
    <div class=”panelcontent”> … </div>
    </div>

    the gap only appears in the first panel; the subsequent panels are perfect. The panel content is identical for all the panels in my mockup and works fine on IE8 and Firefox.  I haven’t checked older versions of IE …
                             

  15. This is really fantastic, by far the best we found – thank you so much!

    You can see it in live production here, it works really well:
    http://prodesigntools.com/products/adobe-cs5-system-requirements.html

    The only (small) addition we made was to change the mouse cursor when hovering over the header to enhance clickability, a la:

    .panel h2:hover, .panelcollapsed h2:hover
    {
    background-color: #A9BCEF;
    cursor:pointer;
    }

    (The new addition to the code is the last line, “cursor:pointer;”)

  16. Armand thanks for the simple plug and play. I may have missed this but bare with me.
    I have a page with around 10 collapsible panels I would like to add an “Expand All” “Close All” link, button or img
    so my users can do this with ease.
    I have even considered, as you may have seen, “Close all but this” but that may be a little more work than time will allow.

    Any thoughts?

    Thanks again,
    Bert Hood

    1. Bert,
      I modified the code and added Expand All / Collapse All. Just download the zip file again. If you already integrated the javascript in your project, just do a diff to see what’s new or add the code
      else panelsStatus[name] = (el.parentNode.className == PANEL_NORMAL_CLASS) ? "true" : "false";
      at line 35
      and the two new functions expandAll() and collapseAll() – they’re are in the downloadable archive but not in the example code above.

  17. Hey there! thanks for this awesome script. I have installed it on my website without any problems :) I was just wondering if it was possible to make the script so that it dosent remember the opened panels when the user revisits the page. I want to remove the whole “cookies” and “remembering” part of the script so when a user revisits or reloads the page, the panels would be collapsed. I am only asking this since I am using this awesome script as a FAQ page. Thanks for your awesome script!

  18. I just found out your collapse JS doesn’t fully work when you have another classname in the ‘panel’ or ‘panelcollapsed’ div.

    You will then have to add the extra class(es) in the PANEL_NORMAL_CLASS and _COLLAPSED_ in the javascript which is quite anoying when you have various div classes with the panel or panelcollapsed class in them.

    1. A friend of mine got it working using 3 regular expresion functions picked from https://www.openjs.com/scripts/dom/class_manipulation.php to keep the script working even when you have multiple classes in the ‘panel’ or ‘panelclosed’ div class.

      Add the following functions:
      function hasClass(ele,cls){return ele.className.match(new RegExp('(\s|^)'+cls+'(\s|$)'));}
      function addClass(ele,cls){if(!this.hasClass(ele,cls))ele.className+=' '+cls;}
      function removeClass(ele,cls){if(hasClass(ele,cls)){var reg=new RegExp('(\s|^)'+cls+'(\s|$)');ele.className=ele.className.replace(reg,' ');}}

      Replace line 22 with:
      if (!hasClass(el.parentNode, PANEL_NORMAL_CLASS) && !hasClass(el.parentNode, PANEL_COLLAPSED_CLASS))
      Replace line 43 with:
      var collapsed = hasClass(target, PANEL_COLLAPSED_CLASS);
      Replace line 61 with:
      if(hasClass(elements[i], PANEL_CONTENT_CLASS))
      Replace lines 102 with:
      // set class for the panel
      removeClass(panelContent.parentNode, !(direction < 0) ? PANEL_COLLAPSED_CLASS : PANEL_NORMAL_CLASS);
      addClass(panelContent.parentNode, (direction < 0) ? PANEL_COLLAPSED_CLASS : PANEL_NORMAL_CLASS);

  19. Hi there,

    Does anyone have any suggestions for coding PHP animated collapse? I’m using WordPress for the first time and I find myself completely incapable of figuring this out on my own. Any help or guidance would be appreciated.

    Thanks!

Comments are closed.