Agent YUI: Mission 4 - Some Animation, Q?

After dealing with the basic modules like DOM and Event during the last couple of Agent YUI tutorials, I am now moving on to the Animation module.

Introduction

All the cool moving, fading and more that you always wanted to put on your Web 2.0 site…piece of cake with YUI Animation. In this example, I am gonna show how to use YAHOO.util.Anim (for changing style properties of an element to create e.g. a sliding effect) and YAHOO.util.Motion (for moving an element around on the page).

The Mission Statement

We have 3 junks of information, each one related to a James Bond actor. Each junk consists of a picture of the actor, a headline and some additional biographical information. What we want to do is:

  • Without JavaScript, all the items should be shown on the page on pageload and presented to the user (screenshot).
  • With JavaScript, the image, as well as the headline should be “pulled in” from off the screen, one after the other. Finally, if the user requires more info about an actor, he can request that by clicking on “get more info” and the respective text will show using another animation effect (screenshot).

Step 1: The Markup

First, as with every other tutorial, we make sure that markup and styling are in place and then add behavior (JavaScript) an top. We start by putting the 3 junks of information on the page, marked up as list items. This would be the one for Sir Sean Connery:

<ul>
    <li id="connery">
       <h2>Sean Connery</h2>
       <a id="connery-get-more" href="http://en.wikipedia.org/wiki/Sean_Connery">Get more info</a>
       <img src="img/sean.jpg" alt="Sean Connery" />
       <p>Sean Connery (born 25 August 1930) is a retired Scottish actor and
       producer who is perhaps best known as...</p>
    </li>
    ...
</ul>

Please check out the demo page for this first step. For screenreaders and people viewing web pages with not that much CSS support, this is acceptable. But as we all know, bare-bones HTML looks ugly, so lets move on to Step 2.

Step 2: Adding some styling

In this step, we are creating the version of the site that should be displayed to users who have CSS support, but lack JavaScript for whatever reason. So we do our best to make the page look like we envisioned it and add some CSS styles to it. As this is about YUI and not about nifty CSS styling, I will not go into much detail at this point but simply let you, my dear reader, observe the result after Step 2 is completed. Still no JavaScript involved, folks.

Step 3a: Animateify the list items

Animateify - what an ugly word. Anyway. We start off putting in animation by including the required YUI files and declaring our module, including an empty init function, and insert the call to the init function, like this:

<script type="text/javascript" src="http://yui.yahooapis.com/2.3.1/build/yahoo-dom-event/yahoo-dom-event.js"></script>
<script type="text/javascript" src="http://yui.yahooapis.com/2.4.1/build/animation/animation-min.js"></script>
<script type="text/javascript">

YAHOO.namespace("AgentYUI");

YAHOO.AgentYUI.Mission4 = function () {
   var init = function () {

   };

   return {
      init: init
   }
}();

YAHOO.AgentYUI.Mission4.init();

The first thing we want to do in the init function is to put together references to the respective list items in an array and also store references to the text displayed for the respective actor:

var items = [
    document.getElementById('connery'),
    document.getElementById('brosnan'),
    document.getElementById('craig')
];

conneryText = items[0].getElementsByTagName('p')[0];
brosnanText = items[1].getElementsByTagName('p')[0];
craigText = items[2].getElementsByTagName('p')[0];

Now we can move on to animating the list items themselves. We want them to move in from offscreen, so what we need to do first, is add some JavaScript that actually positions the items offscreen. We do that in the head of the document (it looks ugly, but does the trick). We also add a couple of other styles that make sure that also the other items don’t appear up front (we want to animate them, remember?):

<script type="text/javascript">
document.write("<style>");
document.write("#connery, #brosnan, #craig { left: -500px }");
document.write("a { display: none } ");
document.write("h2 { width: 0; padding: 0 }");
document.write("p { height: 0 }");
</script>

After that, we can create a function called animateListItems which will change the CSS properties of every item to move to 200px from the left on the screen. We do this with YAHOO.util.Motion, like:

var animateListItems = function () {
    var duration = 1; /* setting the duration for the first animation */
    for (var i in items) {
        var listAnim = new YAHOO.util.Motion(items[i], {points:
            { to: [200, 0] } /* move every item to 200px left from the viewport edge */
        });

        listAnim.duration = duration++;
        listAnim.animate(); /* run animation */
    }
};

What is going on? First we set a duration variable to 1 - this is just an comfortable way to make the list items move in from offscreen one after the other. So the first one will take 1 second, the second one will take 2 seconds and the third one 3 seconds to move to its final position. We then loop through all the items and set the endpoint for every item to 200px from the left of the viewport on the x-axis. This is done for all 3 items subsequently. You can see the result of this step on this demo page.

Step 3b: Animating the Headings

Right after the image is in its place, the respective heading is supposed to appear - animated, of course. YAHOO.util.Anim has an Event onComplete you can subscribe a function to which will be called when the animation is completed. So we add this to our animateListItems function:

var animateListItems = function () {
    ...
       listAnim.onComplete.subscribe(animateHeading); /* we use this hook to initiate the animation of the headline */
       listAnim.duration = duration++;
       listAnim.animate(); /* run animation */
    ...
};

We will obviously use the function animateHeading to make our headline(s) appear. In that function, we first set the CSS properties back to what was originally specified in the CSS:

var actorItem = this.getEl(); // get original element, e.g. <li id="connery">
var currentHeadline = actorItem.getElementsByTagName('h2')[0];
var currentGetMoreLink = actorItem.getElementsByTagName('a')[0];
YAHOO.util.Dom.setStyle(currentHeadline, "padding" , "5px" );
YAHOO.util.Dom.setStyle(currentGetMoreLink, "display" , "inline" );

After that, we animate the heading. This time, we use YAHOO.util.Anim to animate a specific CSS property (the width in that case) to create the desired effect:

var headAnim = new YAHOO.util.Anim(currentHeadline, {
    width: { to: 500 }
    },1, YAHOO.util.Easing.easeOut);

What this does is it takes the headline of the list item currently dealt with and adjusts the width-property to 500px over a period of 1 second. In addition, we are adding a nice easing effect :-)

Thats all about the magic on this part and you can - of course - view a demo.

Step 3c: Making the text appear in a smooth way

The final step is to nicely “slide in” the respective biographical text when the user clicks on “get more info”. So the first thing we need to do is attach Event listeners to every “get more info” link. So we add the following code to the init function:

/* attaching event listeners for "more info" links */
for (var i in items) {
    YAHOO.util.Event.addListener(items[i].getElementsByTagName('a')[0], 'click', animateText);
}

As we would like to remember for each text if it is currently open or not (and a subsequent click on the link should make it disappear again), we need an object for each text. We already defined these in the init function:

conneryText = items[0].getElementsByTagName('p')[0];
brosnanText = items[1].getElementsByTagName('p')[0];
craigText = items[2].getElementsByTagName('p')[0];

In the animateText function we first need to check which of the links has been clicked. We determine which text we need to show by checking whether the id contains either “connery”, “brosnan” or “craig” and based on that, we show the respective text - again, animated :-)

var targetEl = YAHOO.util.Event.getTarget(e);

if (targetEl.id.indexOf("connery")!= -1) {
    animEl = conneryText;
} else if (targetEl.id.indexOf("brosnan")!= -1) {
    animEl = brosnanText;
} else {
    animEl = craigText;
}

We also add an additional property to the object (e.g. conneryText) so that we know if the text is currently expanded or hidden.

if (animEl.isExpanded) {
    options = {
        height: { to: 0 }
    };
    animEl.isExpanded = false;
} else {
    options = {
        height: { to: 130 }
    };
    animEl.isExpanded = true;
}

var textAnim = new YAHOO.util.Anim(animEl, options ,1, YAHOO.util.Easing.easeOut);
textAnim.animate();

And this is it. We managed to animate the image, the headline and the text and also made sure that there is an appropriate version for non-JavaScript (and non-CSS users) in place. Lets have a look at the final result.