Javascript animated collapsible panels without any frameworks

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

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:

11
12
13
14
15
16
17
18
19
20
21
22
23
24
.panel h2, .panelcollapsed h2
{
	font-size: 18px;
	font-weight: normal;
	margin: 0px;
	padding: 4px;
	background: #CCC url(arrow-up.gif) no-repeat 280px;
	border-bottom: 1px solid #999;
	-moz-border-radius: 3px;
	-webkit-border-radius: 3px;
	border-top: 1px solid #FFF;
	border-right: 1px solid #FFF;
	border-left: 1px solid #FFF;
}

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

25
26
27
28
29
.panelcollapsed h2
{
	background: #CCC url(arrow-dn.gif) no-repeat 280px;
	border-color: #CCC;
}

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

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

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

31
32
33
34
35
36
37
.panelcontent
{
	background: #EEE;
	overflow: hidden;
}
 
.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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var PANEL_NORMAL_CLASS    = "panel";
var PANEL_COLLAPSED_CLASS = "panelcollapsed";
var PANEL_HEADING_TAG     = "h2";
 
function setUpPanels()
{
	var headingTags = document.getElementsByTagName(PANEL_HEADING_TAG);
 
	for (var i=0; i < headingTags.length; i++)
	{
		var el = headingTags[i];
		if (el.parentNode.className != PANEL_NORMAL_CLASS && el.parentNode.className != PANEL_COLLAPSED_CLASS)
			continue;
		el.onclick = function()
		{
			var target    = this.parentNode;
			var collapsed = target.className == PANEL_COLLAPSED_CLASS;
			target.parentNode.className = collapsed ?  PANEL_NORMAL_CLASS : PANEL_COLLAPSED_CLASS;
		};
	}
}
 
// Register setUpPanels to be executed on load
if (window.addEventListener)
	window.addEventListener("load", setUpPanels, false);
else
if (window.attachEvent)
	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

4
var PANEL_COOKIE_NAME = "panels";

Saving settings

Saving will take place each time a panel is toggled.

30
31
32
33
34
35
36
37
38
39
40
41
42
function saveSettings(key, value)
{
	panelsStatus[key] = value;
 
	var panelsData = [];
	for (var key in panelsStatus)
		panelsData.push(key+":"+panelsStatus[key]);
 
	var today = new Date();
	var expirationDate = new Date(today.getTime() + 365 * 1000 * 60 * 60 * 24);
 
	document.cookie = PANEL_COOKIE_NAME + "=" + escape(panelsData.join("|")) + ";expires=" + expirationDate.toGMTString();
}

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.

43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
function loadSettings()
{
	panelsStatus = {};
 
	var start = document.cookie.indexOf(PANEL_COOKIE_NAME + "=");
	if (start == -1) return;
	start += PANEL_COOKIE_NAME.length+1;
	var end = document.cookie.indexOf(";", start);
	if (end == -1) end = document.cookie.length;
 
	var cookieValue = unescape(document.cookie.substring(start, end));
	var panelsData = cookieValue.split("|");
 
	for (var i=0; i< panelsData.length; i++)
	{
		var pair = panelsData[i].split(":");
		panelsStatus[pair[0]] = pair[1];
	}
}

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:

5
6
7
var PANEL_CONTENT_CLASS   = "panelcontent";
var PANEL_ANIMATION_DELAY = 20; /*ms*/
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.

65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
function animateTogglePanel(panel, expanding)
{
	var elements = panel.getElementsByTagName("div");
	var panelContent = null;
	for (var i=0; i < elements.length; i++)
	{
		if (elements[i].className == PANEL_CONTENT_CLASS)
		{
			panelContent = elements[i];
			break;
		}
	}
 
	panelContent.style.display = "block";
	var contentHeight = panelContent.offsetHeight;
 
	if (expanding)
		panelContent.style.height = "0px";
 
	var stepHeight = contentHeight / PANEL_ANIMATION_STEPS;
	var direction = (!expanding ? -1 : 1);
 
	setTimeout(function(){animateStep(panelContent,1,stepHeight,direction)}, PANEL_ANIMATION_DELAY);
}

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.

89
90
91
92
93
94
95
96
97
98
99
100
101
102
function animateStep(panelContent, iteration, stepHeight, direction)
{
	if (iteration < PANEL_ANIMATION_STEPS)
	{
		panelContent.style.height = Math.round(((direction > 0) ? iteration : PANEL_ANIMATION_STEPS - iteration) * stepHeight) +"px";
		iteration++;
		setTimeout(function(){animateStep(panelContent,iteration,stepHeight,direction)}, PANEL_ANIMATION_DELAY);
	}
	else
	{
		panelContent.parentNode.className = (direction < 0) ? PANEL_COLLAPSED_CLASS : PANEL_NORMAL_CLASS;
		panelContent.style.display = panelContent.style.height = "";
	}
}

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

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

35 replies
  1. Mark McDonnell
    Mark McDonnell says:

    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. Alex
    Alex says:

    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. john
    john says:

    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

    • john
      john says:

      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. Daniel
    Daniel says:

    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. Gerard
    Gerard says:

    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. SCOOT JOHNSON
    SCOOT JOHNSON says:

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

  7. hanene
    hanene says:

    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.

    • Armand Niculescu
      Armand Niculescu says:

      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. hanene
    hanene says:

    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. hanene
    hanene says:

    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.

    • Armand Niculescu
      Armand Niculescu says:

      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. shaun
    shaun says:

    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. Jesse W
    Jesse W says:

    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!

    • Armand Niculescu
      Armand Niculescu says:

      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. designer
    designer says:

    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. Tom McAdams
    Tom McAdams says:

    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. Martin Arnold
    Martin Arnold says:

    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. ProDesignTools
    ProDesignTools says:

    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. Bert
    Bert says:

    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

    • Armand Niculescu
      Armand Niculescu says:

      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. custy1234
    custy1234 says:

    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. Wiethoofd
    Wiethoofd says:

    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.

    • Wiethoofd
      Wiethoofd says:

      A friend of mine got it working using 3 regular expresion functions picked from http://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. Deven Chism
    Deven Chism says:

    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.