Вы находитесь на странице: 1из 11

Javascript animated collapsible panels without any frameworks

Javascript animated collapsible panels without any frameworks


By Armand Niculescu If you need to make collapsible panels that remember their state, but dont want to use any javascript framework, this tutorial will help you do this with no headaches - and youll learn some neat javascript tricks too! In case youre wondering why no frameworks, the reason is two-fold. First, although frameworks are nice, theyre BI . j!uery weighs over "#$b %& its not much if youre building an entire web app, but if youre only looking for some specific effect, its overkill considering that you can achieve the same thing in less that '$b. Second, by doing it yourself, you have the potential to actually learn more about javascript (only if you want to ) the code Im presenting is pretty much plug-andplay* and reuse the concepts to do other things - like a collapsible tree menu for e+ample.

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 e+panded-collapsed state& animated transitions& each panels state is saved and remembered between sessions& tested in I./, I.0, I.1, 2irefo+ ', 3hrome, 4afari 5 and 6pera 7.

Start with layout and style


2or this tutorial well use a minimal style& since the article is not about 344, I will only briefly cover this.

HTML
8he re9uired :8;< for a panel is very simple=
1. <div class="panel"> 2. <h2>One panel</h2> % 8o add insult to injury, if you are using a 3;4 or blog with plugins, they sometimes use different frameworks, so the page ends up loading j!uery, ;oo8ools and 4criptaculous to display some simple effects.

>?##7 @ichAetBpps.com

Javascript animated collapsible panels without any frameworks 3. <div class="panelcontent"> 4. ... content goes here ... 5. </div> 6. </div>

Ce 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. Ce need this kind of nested structure, but if anyone thinks of a more semantic approach, it can be changed.

!SS
2or our panel we will use two main classes, .panel (for e+panded state* and
.panelcollapsed (for the collapsed state*, like this=
1. .panel, .panelcollapsed 2. { 3. background: #eee 4. margin: 5p! 5. padding: "p! "p! 5p! 6. width: 3""p! #. border: 1p! sol$d #%%% &. '(o)'*order'rad$+s: 4p! %. ',e*-$t'*order'rad$+s: 4p! 1". .

2eel free to change the layout as you see fit. Aote that in the e+ample above 2irefo+ and Cebkit-based browsers (3hrome and 4afari* will also get some nice rounded corners. 2or headings Im using an arrow icon on the background, the rest of the style is pretty plain=
11. .panel h2, .panelcollapsed h2 12. { 13. font-size: 1&p! 14. font-weight: nor(al 15. margin: "p! 16. padding: 4p! 1#. background: #/// +rl0arrow-up.gif1 no'repeat 2&"p! 1&. border-bottom: 1p! sol$d #%%% 1%. '(o)'*order'rad$+s: 3p! 2". ',e*-$t'*order'rad$+s: 3p! 21. border-top: 1p! sol$d #222 22. border-right: 1p! sol$d #222 23. border-left: 1p! sol$d #222 24. .

2or a collapsed panel, we want to change the style slightly and change the arrow direction=
25. .panelcollapsed h2 26. { 2#. background: #/// +rl0arrow-dn.gif1 no'repeat 2&"p! 2&. border-color: #/// 2%. .

>?##7 @ichAetBpps.com

Javascript animated collapsible panels without any frameworks

"

I also wanted to add a little Drollover effect on the heading (not visible in I./*=
3". .panel h2:ho3er, .panelcollapsed h2:ho3er { background-color: #4%5/62 .

2inally, well add just a little style for the panel content=
31. 32. 33. 34. 35. 36. 3#. .panelcontent { background: #666 overflow: h$dden . .panelcollapsed .panelcontent { display: none .

#dding the interactivity


Because I wanted the :8;< to be as simple as possible and to work with any number of panels, I did not add links with onclick handlers (e.g. a href!"#" onclick!"toggle$%"&* and I also didnt want to have to add IEs to panels. 8herefore, I made the script smart enough to discover the panels and add the onclick handlers by itself. 8his is not as hard as it may seem=
1. 2. 3. 4. 5. 6. #. &. %. 1". 11. 12. 13. 14. 15. 16. 1#. 1&. 1%. 2". 21. 22. 23. 24. 25. 2'. 2#. 2&. var 74869:8O;<49:/94== = "panel" var 74869:/O9947=6>:/94== = "panelcollapsed" var 74869:?64>@8A:B4A = "h2" function setCp7anels01 { var head$ngBags = doc+(ent.get6le(ents5DBag8a(e074869:?64>@8A:B4A1 for 0var $=" $ < head$ngBags.length $EE1 { var el = head$ngBagsF$G if 0el.parent8ode.class8a(e H= 74869:8O;<49:/94== II el.parent8ode.class8a(e H= 74869:/O9947=6>:/94==1 continue el.oncl$c- = function01 { var target = this.parent8ode var collapsed = target.class8a(e == 74869:/O9947=6>:/94== target.parent8ode.class8a(e = collapsed J 74869:8O;<49:/94== : 74869:/O9947=6>:/94== . . . // Register setUpPanels to be executed on load if 0,$ndo,.add63ent9$stener1 ,$ndo,.add63ent9$stener0"load", setCp7anels, false1 else if 0,$ndo,.attach63ent1 ,$ndo,.attach63ent0"onload", setCp7anels1

>?##7 @ichAetBpps.com

Javascript animated collapsible panels without any frameworks

2irst, Im setting up some constants, just in case youll want to use different tags for the heading or different class names. 8he set(p)anels function works like this= %. using get*lements+y,ag-ame we get all H2 headings in the page and collect them into an array& ?. we go through each collected element= %. test to see if its inside a panel by accessing its parent class name, skip if its not panel or panelcollapsed. ?. add an onclick handler for the heading. 8his will work fine even without a link, although the hover effect will not work in I./. 8he function does the following= %. get the headings parent, thats the panel itself. ?. get its class name, switch between panel and panelcollapsed. 2inally, we need a way to make the set(p)anels to e+ecute upon window load. Aow, instead of using a body onload!"set(p)anels$%"& construct, well use something more clever. Fnfortunately, as always I. plays different, so well have to use two methods= the correct E6; method via add*vent.istener and the I. way with attach*vent.

Making changes persistent


8o be really useful for the user, the e+panded-collapsed preferences should be saved in a cookie, so that the ne+t time the page is loaded, the panels are already e+panded or collapsed. 2or this, first well use an object called panels/tatus that will keep the e+panded-collapsed status for each panel& it will act as an associative array or a hash. 8he Dkey will be the panels name (the te+t in the H2 tag* and the value will be GtrueH for an e+panded panel and GfalseH for a collapsed one. 8o keep code neat, well also define our cookie name with
2%. var 74869:/OOK@6:84<6 = "panels"

Saving settings
4aving will take place each time a panel is toggled.
3". function sa3e=ett$ngs0-eD, 3al+e1 31. { 32. panels=tat+sF-eDG = 3al+e 33. 34. var panels>ata = FG 35. for 0var -eD in panels=tat+s1 36. panels>ata.p+sh0-eDE":"Epanels=tat+sF-eDG1 3#. 3&. var todaD = new >ate01 3%. var e!p$rat$on>ate = new >ate0todaD.getB$(e01 E 365 L 1""" L 6" L 6" L 241 4".

>?##7 @ichAetBpps.com

Javascript animated collapsible panels without any frameworks 41. doc+(ent.coo-$e = 74869:/OOK@6:84<6 E "=" E escape0panels>ata.Mo$n0"N"11 E " e!p$res=" E e!p$rat$on>ate.toA<B=tr$ng01 42. .

4tep by step, heres what the function does= %. get the modified key-value pair and put it in the panels/tatus object& ?. go through all elements in the object and combine entries in a string like this= Gkey0valueG, put in a temporary array& '. get today date and add one year to it (that will be the cookie e+piration date*& 5. join the temporary array elements into one string like Gkey10value12key20value22 key30value3H and write in the cookie.8he cookie string will look like this= panels=4ne526panel537false;expires=/un8 11 7pr 2616 690:6022 ;<, 2or simplicity Ive used G=H (colon* and GIH (pipe* as separators. If you think youre likely to encounter those in the panel name, you can replace them with any weird character you can think of.

Loading settings
<oading will be done once, when the page is loaded.
43. function load=ett$ngs01 44. { 45. panels=tat+s = {. 46. 4#. var start = doc+(ent.coo-$e.$nde!OO074869:/OOK@6:84<6 E "="1 4&. if 0start == '11 return 4%. start E= 74869:/OOK@6:84<6.lengthE1 5". var end = doc+(ent.coo-$e.$nde!OO0" ", start1 51. if 0end == '11 end = doc+(ent.coo-$e.length'1 52. 53. var coo-$ePal+e = +nescape0doc+(ent.coo-$e.s+*str$ng0start, end11 54. var panels>ata = coo-$ePal+e.spl$t0"N"1 55. 56. for 0var $=" $< panels>ata.length $EE1 5#. { 5&. var pa$r = panels>ataF$G.spl$t0":"1 5%. panels=tat+sFpa$rF"GG = pa$rF1G 6". . 61. .

:eres what the code does= %. ?. '. 5. ". create the panels/tatus object& find the cookie name in the document.cookie string, return if not found& find the end of the cookie value string (before the G=e>pires!G*& take the cookie value and split by by the GIH character, put in a temporary array& go through each element in the array, split by G=H and save in the panels/tatus object as key-value pair.

>?##7 @ichAetBpps.com

Javascript animated collapsible panels without any frameworks

&

#nimating the transition


2or some reason, people think animation is difficult in browser. Its not. 8hink of it this way= any property that you can set via 344, you can alter dynamically in javascript. 8o animate, you just need to call a function repeatedly, and this is most easily achieved with set,imeout or setInterval(*.

'reparing the transition


2irst, lets define some new constants=
62. var 74869:/O8B68B:/94== = "panelcontent" 63. var 74869:48@<4B@O8:>694Q = 2" /*ms*/ 64. var 74869:48@<4B@O8:=B67= = 1"

4o, 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. 8o keep things as fle+ible as possible, I wanted the script to work easily regardless of the panel height, so when animating, well dynamically change the height of the panel content (thats why it needs the overflow0hidden definition in 344*. If you want all panels to have the same height, maybe with scrollable content, I suggest adding another EIJ in the panel content and setting its height and overflow.
65. function an$(ateBoggle7anel0panel, e!pand$ng1 66. { 6#. var ele(ents = panel.get6le(ents5DBag8a(e0"d$3"1 6&. var panel/ontent = null 6%. for 0var $=" $ < ele(ents.length $EE1 #". { #1. if 0ele(entsF$G.class8a(e == 74869:/O8B68B:/94==1 #2. { #3. panel/ontent = ele(entsF$G #4. break #5. . #6. . ##. #&. panel/ontent.stDle.d$splaD = "*loc-" #%. var content?e$ght = panel/ontent.oOOset?e$ght &". &1. if 0e!pand$ng1 &2. panel/ontent.stDle.he$ght = ""p!" &3. &4. var step?e$ght = content?e$ght / 74869:48@<4B@O8:=B67= &5. var d$rect$on = 0He!pand$ng J '1 : 11 &6. &#. setB$(eo+t0function01{an$(ate=tep0panel/ontent,1,step?e$ght,d$rect$on1., 74869:48@<4B@O8:>694Q1 &&. .

>?##7 @ichAetBpps.com

Javascript animated collapsible panels without any frameworks

8his function e+pects two parameters - a reference to the panel that needs to be animated and whether it should be an e+panding or collapsing animation. %. 2irst, 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. 8his process could have been simplified a little by using get*lements+y?lass-ame, but that function is not supported by I. (surprise, surprise*. ?. 8hen, we need to get the height of the panel content, so we make sure its displayed by setting its display property to GblockH and read its height with offsetHeight property. offsetHeight return its total height, meaning defined height K paddings K border widths, so you should not define any paddings or borders in the panelcontent class, or youll get inaccurate results. '. if the animation is for e+panding, we need the content visible, but it has to start with a height of 6. 5. calculate by how much the panel should e+pand or contract on each step by dividing the total height by the number of steps, and the direction - positive for e+pansion, negative for contraction ". set the timeout - heres a tricky thing= we cant call animate/tep function directly because we need to pass the reference to the panel content to it. 4o we set an anonymous function, which in turn will call animate/tep. Its confusing, but it works for more details, read about setInterval callback arguments on ;oLilla Eeveloper. 8he ne+t function is called every ?# milliseconds. It receives a reference to the panel content, the current step (iteration number*, the step height and direction.
&%. function an$(ate=tep0panel/ontent, $terat$on, step?e$ght, d$rect$on1 %". { %1. if 0$terat$on < 74869:48@<4B@O8:=B67=1 %2. { %3. panel/ontent.stDle.he$ght = <ath.ro+nd000d$rect$on > "1 J $terat$on : 74869:48@<4B@O8:=B67= ' $terat$on1 L step?e$ght1 E"p!" %4. $terat$onEE %5. setB$(eo+t0function01 {an$(ate=tep0panel/ontent,$terat$on,step?e$ght,d$rect$on1., 74869:48@<4B@O8:>694Q1 %6. . %#. else %&. { %%. panel/ontent.parent8ode.class8a(e = 0d$rect$on < "1 J 74869:/O9947=6>:/94== : 74869:8O;<49:/94== 1"". panel/ontent.stDle.d$splaD = panel/ontent.stDle.he$ght = "" 1"1. . 1"2. .

Chat it does= %. checks if the current iteration is smaller than the total number of iterations& ?. if it is= %. change the panel content height to be e9ual to iteration number multiplied by step height& reverse if its a collapsing animation&for e+ample, consider it>?##7 @ichAetBpps.com

Javascript animated collapsible panels without any frameworks

eration ? with a step height of "p+, e+panding. Manel content height becomes ?N"O%#p+& at iteration ' it will be 'N"O%"p+ and so on&for a collapsing animation, if the total number of steps is %#, at iteration ? wed have (%#-?*N" O5#p+, iteration ' is (%#-'*N"O'"p+. ?. set the timeout to call animate/tep function again after the specified delay. '. if its not= %. switch the class name for the panel (panel contents parent*. ?. clear any inline styles weve set.

'utting it all together


8hats it really. Bll we need to do now is to integrate the load-save and animation back into our first code. 8he final javascript will look like this=
1. 2. 3. 4. 5. 6. #. &. %. 1". 11. 12. 13. 14. 15. 16. 1#. 1&. 1%. 2". 21. 22. var var var var var var var 74869:8O;<49:/94== 74869:/O9947=6>:/94== 74869:?64>@8A:B4A 74869:/O8B68B:/94== 74869:/OOK@6:84<6 74869:48@<4B@O8:>694Q 74869:48@<4B@O8:=B67= = = = = = = = "panel" "panelcollapsed" "h2" "panelcontent" "panels" 2" /*ms*/ 1"

function setCp7anels01 { load=ett$ngs01 // get all headings var head$ngBags = doc+(ent.get6le(ents5DBag8a(e074869:?64>@8A:B4A1 // go through all tags for 0var $=" $ < head$ngBags.length { var el = head$ngBagsF$G $EE1

// make sure it s the heading inside a panel if 0el.parent8ode.class8a(e H= 74869:8O;<49:/94== II el.parent8ode.class8a(e H= 74869:/O9947=6>:/94==1 23. continue 24. 25. // get the text !alue of the tag 26. var na(e = el.O$rst/h$ld.nodePal+e 2#. 2&. // look for the name in loaded settings" appl# the normal/collapsed class 2%. el.parent8ode.class8a(e = 0panels=tat+sFna(eG == "Oalse"1 J 74869:/O9947=6>:/94== : 74869:8O;<49:/94== 3". 31. // add the click beha!or to headings 32. el.oncl$c- = function01 33. { 34. var target = this.parent8ode 35. var na(e = this.O$rst/h$ld.nodePal+e

>?##7 @ichAetBpps.com

Javascript animated collapsible panels without any frameworks 36. 3#. 3&. 3%. 4". 41. 42. $3. 44. 45. 46. 4#. 4&. 4%. 5". 51. 52. 53. 54. 55. 56. 5#. 5&. 5%. 6". 61. 62. 63. 64. 65. 66. 6#. 6&. 6%. #". #1. #2. #3. #4. var collapsed = target.class8a(e == 74869:/O9947=6>:/94== sa3e=ett$ngs0na(e, Hcollapsed.to=tr$ng011 an$(ateBoggle7anel0target, collapsed1 . . . /** * %tart the expand/collapse animation of the panel * &param panel reference to the panel di! */ function an$(ateBoggle7anel0panel, e!pand$ng1 { // find the .panelcontent di! var ele(ents = panel.get6le(ents5DBag8a(e0"d$3"1 var panel/ontent = null for 0var $=" $ < ele(ents.length $EE1 { if 0ele(entsF$G.class8a(e == 74869:/O8B68B:/94==1 { panel/ontent = ele(entsF$G break . . // make sure the content is !isible before getting its height panel/ontent.stDle.d$splaD = "*loc-" // get the height of the content var content?e$ght = panel/ontent.oOOset?e$ght // if panel is collapsed and expanding" we must start with ' height if 0e!pand$ng1 panel/ontent.stDle.he$ght = ""p!" var step?e$ght = content?e$ght / 74869:48@<4B@O8:=B67= var d$rect$on = 0He!pand$ng J '1 : 11

setB$(eo+t0function01{an$(ate=tep0panel/ontent,1,step?e$ght,d$rect$on1., 74869:48@<4B@O8:>694Q1 #5. . #6. ((. /** #&. * )hange the height of the target #%. * &param panel)ontent reference to the panel content to change height &". * &param iteration current iteration* &1. * &param step+eight height increment to be added/substracted in one step &2. * &param direction , for expanding" -, for collapsing &3. */ &4. function an$(ate=tep0panel/ontent, $terat$on, step?e$ght, d$rect$on1 &5. { &6. if 0$terat$on < 74869:48@<4B@O8:=B67=1 &#. { &&. panel/ontent.stDle.he$ght = <ath.ro+nd000d$rect$on > "1 J $terat$on : 74869:48@<4B@O8:=B67= ' $terat$on1 L step?e$ght1 E"p!" &%. $terat$onEE %". setB$(eo+t0function01

>?##7 @ichAetBpps.com

Javascript animated collapsible panels without any frameworks {an$(ate=tep0panel/ontent,$terat$on,step?e$ght,d$rect$on1., 74869:48@<4B@O8:>694Q1 . else { // set class for the panel panel/ontent.parent8ode.class8a(e = 0d$rect$on < "1 J 74869:/O9947=6>:/94== : 74869:8O;<49:/94== // clear inline st#les panel/ontent.stDle.d$splaD = panel/ontent.stDle.he$ght = "" . .

1+

%1. %2. %3. %4. %5. %6. %#. %&. %%. 1"". ,',. ,'2. ,'3. ,'$. 1"5. ,'1. 1"#. 1"&. 1"%. 11". 111. 112. 113. 114. 115. 116. 11#. 11&. 11%. 12". 121. 122. 123. 124. 125. 126. 12#. 12&. 12%. 13". 131. 132. 133. 134. 135. ,31. 13#. ,34. 13%. 14". 141. 142. 143. 144.

// -----------------------------------------------------------------------------// -oad-%a!e // -----------------------------------------------------------------------------/** * Reads the .panels. cookie if exists" expects data formatted as ke#/!alue0 * puts in panels%tatus ob2ect */ function load=ett$ngs01 { // prepare the ob2ect that will keep the panel statuses panels=tat+s = {. // find the cookie name var start = doc+(ent.coo-$e.$nde!OO074869:/OOK@6:84<6 E "="1 if 0start == '11 return // starting point of the !alue start E= 74869:/OOK@6:84<6.lengthE1 // find end point of the !alue var end = doc+(ent.coo-$e.$nde!OO0" ", start1 if 0end == '11 end = doc+(ent.coo-$e.length'1 // get the !alue" split into ke#/!alue pairs var coo-$ePal+e = +nescape0doc+(ent.coo-$e.s+*str$ng0start, end11 var panels>ata = coo-$ePal+e.spl$t0"N"1 // split each ke#/!alue pair and put in ob2ect for 0var $=" $< panels>ata.length $EE1 { var pa$r = panels>ataF$G.spl$t0":"1 panels=tat+sFpa$rF"GG = pa$rF1G . . /** * 3akes data from the panels%tatus ob2ect" formats as ke#/!alue0ke#/!alue... * and puts in cookie !alid for 315 da#s * &param ke# ke# name to sa!e * &paeam !alue ke# !alue */ function sa3e=ett$ngs0-eD, 3al+e1 { // put the new !alue in the ob2ect

>?##7 @ichAetBpps.com

Javascript animated collapsible panels without any frameworks 145. 146. 14#. 14&. 14%. 15". 151. 152. 153. 154. 155. 156. panels=tat+sF-eDG = 3al+e // create an arra# that will keep the ke#/!alue pairs var panels>ata = FG for 0var -eD in panels=tat+s1 panels>ata.p+sh0-eDE":"Epanels=tat+sF-eDG1

11

// set the cookie expiration date , #ear from now var todaD = new >ate01 var e!p$rat$on>ate = new >ate0todaD.getB$(e01 E 365 L 1""" L 6" L 6" L 241 // write the cookie doc+(ent.coo-$e = 74869:/OOK@6:84<6 E "=" E escape0panels>ata.Mo$n0"N"11 E " e!p$res=" E e!p$rat$on>ate.toA<B=tr$ng01 15#. . 15&. ,56. // --------------------------------------------------------------------------,1'. // Register setUpPanels to be executed on load 161. if 0,$ndo,.add63ent9$stener1 162. { ,13. // the .proper. wa# 164. ,$ndo,.add63ent9$stener0"load", setCp7anels, false1 165. . 1''. else 16#. if 0,$ndo,.attach63ent1 16&. { ,16. // the 78 wa# 1#". ,$ndo,.attach63ent0"onload", setCp7anels1 1#1. .

Bs you can see, the code is fully commented, so any details Ive left out you should understand easily.

,ownload the code


Pou can download a fully working e+ample by going to the online article at http=--www.richnetapps.com-QpO'17.

>?##7 @ichAetBpps.com