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.
I skipped to the end to see the complete demo, so I haven’t gone through this properly yet, but..
You might want to edit your last example so that you reference the Yahoo! hosted YUI files rather than the ones that live inside your LAN
Also, document.write in an XHTML document? Isn’t it a little silly to depend on a DOM 0 feature that most, if not all, browsers with an XHTML mode won’t support in that mode?
@David
That’s the problem when you are writing that on an airplane and rushing to get it on the page. Corrected.
ad 1.) Thanks for pointing that out
ad 2.) I totally agree, but for the sake of quickly getting it done (and simplicity and laziness probably), I choose document.write. But I know it is ugly. I could have gotton all the elements on the page separately and then set their styling properties using YUI, for example. David, if you have a smarter idea to do this (which I think you have
) please let me know (bearing in mind that it should not be too overwhelming for a beginners tutorial).
I don’t really object to document.write (although I think DOM methods are cleaner), but combining it with XHTML is very troublesome (and will break if the XHTML is ever treated as such).
I haven’t investigated methods of injecting stylesheets into the page with JavaScript, but it would be an interesting subject for some research.
Timing is a factor, since you would want the CSS to be applied before the page started to be rendered. So how to browsers handle style elements being added before the head element is finished, or just after the body element is started?
It would be worth poking around with a search engine to see if anyone has documented this already, and if not, to write some test cases and see what happens. I might do it myself if I find myself with some free time.
Of course, doing all that would take some time. The quick and easy solution would be to switch to HTML 4.01, which works in browsers without hacks (while XHTML 1.0 as text/html might be blessed by the RFC, I’m of the opinion that it is still a hack).
How can I make an action to start AFTER all the animation has finished??? I mean, if i’m using the Motion, how can I control that after the element gets to the end point, an actions starts up!??
Right now I’m using the onComplete event, but it fires much before the element finishes its animation.
thanx.