Flash Without Flash: Accordion

I guess I’ll start a series “Flash Without Flash” that tries to mimic flash effects using Mootools. It’s easier to copy something than it is to come up with something new, plus it’ll show that there are alternatives to Flash. This time around, I’ll be creating an accordion effect similar to the one found at Openfield Creative. Here’s the final product.

This is the structure I finally settled upon:

<div id="accordion">
  <div class="section">
    <div class="wrapper">
      <div class="content">
        <h2>Header</h2>
        <p>Description</p>
      </div>
      <img src="img1.jpg" alt="image" />
    </div>
  </div>
  ...
</div>

Repeat as many sections as you like. The structure inside the section div is simply to recreate the Openfield layout. It’s not necessary; change it if you like.

Much of the CSS is used to recreate the structure, too. But there are a few key classes.

body {
  background-color:#66B9CC;
  font-size:75%;
  font-family:Arial, sans-serif;
}
#accordion .section {
  overflow:hidden;
  height:200px;
  margin:2px;
  background-color:#D9EEF2;
}
#accordion .section .wrapper {
  width:1500px;
}
#accordion .section .content {
  float:left;
  width:250px;
  padding:0 10px 10px 100px;
  color:#960;
}
#accordion .section .content h2 {
  margin:15px 0 20px;
}
#accordion .small {
  cursor:pointer;
  height:50px;
}
#accordion .small:hover {
  background-color:#fff;
}
#accordion .hidden {
  margin:0;
  height:0;
}

We’ll be using #accordion .small and #accordion .hidden to change the display of our sections as they’re moved to the edges and finally disappear. Since we won’t be using the display property to hide the sections, the margin must be set to zero. If you plan on adding any borders, those will have to be reset also. Each section’s overflow has to be hidden because the height will be adjusted, and we don’t want the content showing through.

Since this is more complex, I’ll be storing all the functions and variables in an object, which should generally be done to prevent the namespace from becoming polluted.

var OFAccordion = {
}

I’ve called it OFAccordion because it’s an accordion in the style of Openfield’s. You can call it whatever makes sense to you.

We’ll start off with the initialize function that we’ll call to get it all going.

start: function () {
  this.sections = $$('#accordion .section');
  this.fx = new Fx.Elements(this.sections, {
    wait:false,
    duration:500,
    transition:Fx.Transitions.Circ.easeOut
  });
  this.sections.setOpacity(0.25).addClass('hidden');
  this.active = false;
  this.current = 0;
  this.o = {
    phase1:{},
    phase2:{}
  };
  this.display(0);
},

First we grab all of the sections and make a new Fx.Elements with them. This allows us to start effects on any or all of the elements at the same time. Check out the Mootools docs for more info on that. I hadn’t used it until now, and it’s pretty handy for stuff like this. Next we set the opacity of all the sections to 25% and add the class hidden. Then we initialize a few variables: active, which we’ll use later to determine whether the effect is active or not; current to store the current section that’s visible; and o.phase1 and o.phase2 to store our effect values which will be chained later, thus the two phase objects. Everything will become clearer as we go along. Finally, we call a function (that hasn’t been defined yet) that will show whatever section index we pass in. In this case, it’s the first section.

Next is the display function:

display: function (index){
  this.active = true;
  this.o.phase1 = {};
  this.o.phase2 = {};
  this.hideOuter(index - 2);
  this.hideOuter(index + 2);
  this.hideInner(index - 1);
  this.hideInner(index + 1);
  this.sections[index].removeClass('small')
   .removeClass('hidden')
   .removeEvents('click')
   .setOpacity(1);
  this.o.phase1[index] = {height:200};
  this.fx.start(this.o.phase1).chain(function () {
    this.fx.start(this.o.phase2);
  }.bind(this)).chain(function () {
    this.active = false;
  }.bind(this));
},

First, we set active to true so later on, we can check it the effect is already in progress to make sure we don’t start it again. Then we clear out the effect values. Now we call a few functions that aren’t defined yet. Essentially, they prepare the sections for the transition and populate the the effect values. After that, the section to be displayed is prepared by removing the small and hidden classes, setting the opacity to 100%, and removing all onClick events. The effect value is set to transition the section’s height to 200. You can change this to the scroll height of the section if you like, but for this example, I wanted the sections to be a uniform height. Finally we start the effect by passing in the effect options for phase one, then chain the effect for phase two, and finally chain a function to set active to false. I know the syntax for chaining looks a little complex, but it’s pretty useful. Take a look at the chaining docs to get a better idea of how it works. We have to use some binding so when the function is executed this refers correctly to the effect.

Now we have a couple helper functions:

displayNext: function () {
  if (this.active == true) return;
  this.display(++this.current);
},
displayPrevious: function () {
  if (this.active == true) return;
  this.display(--this.current);
},

These functions will be attached as click events to the upper and lower sections. All they do is see if the effect is active. If it’s not, call display() and pass a decremented or incremented current index value.

These last couple functions prepare the sections to be hidden or shown smaller:

hideOuter: function(index) {
  if (this.sections[index]) {
    this.sections[index].removeClass('small')
     .addClass('hidden')
     .setStyle('visibility', 'hidden')
     .removeEvents('click');
    this.o.phase1[index] = {height:0};
  }
},
hideInner: function(index) {
  if (this.sections[index]) {
    this.o.phase1[index] = {height:50};
    this.o.phase2[index] = {opacity:0.25};
    this.sections[index].removeClass('hidden')
     .addClass('small')
     .setStyle('visibility', 'visible');
    if (index < this.current) {
      this.sections[index].addEvent('click', this.displayPrevious.bind(this));
    } else {
      this.sections[index].addEvent('click', this.displayNext.bind(this));
    }
  }
}

In hideOuter(), we make sure it has the class of hidden and set its visibility to hidden. The only reason we have to do this here and not in the CSS is because Mootools changes the visibility property when adjusting opacity. Any click events that may have been assigned are removed, and the transition to a height of zero is set. The hideInner() function makes the section appear with a height of 50 and after that, an opacity of 25%. We also need to make sure it’s visible. Finally, we add a click event that will display the next or previous section.

Rememer, to get everything started we need to do this:

window.addEvent('domready', function(){
  OFAccordion.start();
});

I use the domready event that Mootools provides instead of a load event because we have a lot of images, and the script won’t do anything until they’re all loaded if we use a load event. With the domready event, the DOM is completely loaded so we can start manipulating it before the images themselves have loaded.

Here is the demo again.

Now for my critique of this method. This type of accordion only displays three sections at a time; the user can’t navigate to any sections that aren’t adjacent to the current one. Such linear viewing is probably best with wizards and forms, not displaying content that users are likely to want to jump around to. Just keep that in mind if you choose to use this.

7 Responses to “Flash Without Flash: Accordion”

  1. chris

    Small annoyance I found w/ the mootools setup. When you go to click on an accordion tab to bring out another leaf of it, if you leave your cursor on the leaf and try to click to go to the next one, sometimes you have to click twice. I think the code is look for another event before it allows another click, may be my browser, but still. AJAX is for increasing compatibility so.. I’m using latest FireFox FYI

  2. Chris

    I think what you are experiencing can be contributed to the fact that the script doesn’t allow another effect to be started until the current one is completed. It’s not a bug. You might be able to switch the order of the chaining and set active to false before the phase two effects are started.

    As it is now, the outer leaf that used to be the main section has to fade out before the user can click again. There’s probably a better way to do it, but I experienced problems when allowing the effect to be initiated again before it was complete and didn’t really look for a solution.

    Thanks for the feedback!

  3. Daniel

    Hey, is there any way to do this accordian horizontally?

  4. Chris

    Sure it’s possible, but if you mean, “Will you make me a horizontal accordion?” I don’t have the time right now, sorry.

  5. Faramarz

    Hey Chris! Great site, i’m glad i came across this tutorial.

    One question i have, if you can assist me. I would like the item contents to be all hidden at fist (the the accordion collapsed)

    Where would i add the ‘hidethis’ element?

    Also, what would i need to modify to enable all 6 menu items shows. not just one before and one after

    Thanks!

  6. Chris

    Faramarz,

    If you don’t want the first one to be visible, just remove the last line from OFAccordian.start().

    As for your second question, I’m not really sure why you’d want to use this script to show more than one menu item at a time because that’s specifically what it was designed to do. You could try removing the calls to hideOuter and hideInner in OFAccordian.display() and see what happens.

  1. chromaSYNTHETIC Journal » Blog Archive » Six Flash Effects for JavaScript
    Pingback on September 25th, 2007 at 9:20 am

Leave a Reply