The Basics of Effects Consider an element that’s 50 pixels square and absolutely positioned at the top-left cor- ner of the page, as in Figure 10-1. /* CSS: */ #box { position: absolute; width: 50px; height: 50px; top: 0; left: 0; background-color: #999; border: 2px solid #000; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; text-transform: uppercase; text-align: center; line-height: 50px; font-size: 10px; } <! HTML: > <div id="box">Box</div> Figure 10-1. An absolutely positioned div If we want to move it down and to the right, we can do so instantaneously: var box = $('box'); box.setStyle({ left: '50px', top: '50px' }); Here, we’re changing the element’s left and top properties from 0px to 50px. When we set the new style, it happens instantaneously—the box jumps down to its new place, as in Figure 10-2. CHAPTER 10 ■ INTRODUCTION TO SCRIPT.ACULO.US EFFECTS 217 Figure 10-2. The div’s style has been dynamically altered. But let’s try moving the element gradually—by increasing the top and left values little by little until we arrive at the desired 50px. Our first attempt might look something like this: var box = $('box'); for (var i = 1; i <= 50; i++) box.setStyle({ left: i + 'px', top: i + 'px' }); Unfortunately, when we run this code, we find that it behaves exactly the same way as the first example—the box seems to jump to its new coordinates without any steps in the middle. Why? Take a moment to think like a computer. It doesn’t take that much time to change a CSS property—let’s say around 1ms. Multiply that by 50 and you’ve got 50ms, still less than one-tenth of a second. The animation is happening, but far too fast for a human to notice. It might even be happening faster than the rendering engine can update. But speed problems are easy to work around. We need to slow things down by paus- ing after each frame of the animation so that our human eyes (and the browser’s rendering engine) can catch up. Remember that JavaScript has no sleep statement; we can’t tell the interpreter to halt, but we can tell it to do other things for a while, and that’s good enough: function incrementBox(value) { $('box').setStyle({ left: value + 'px', top: value + 'px' }); if (value < 50) incrementBox.delay(0.01, ++value); } incrementBox(1); CHAPTER 10 ■ INTRODUCTION TO SCRIPT.ACULO.US EFFECTS218 Remember Function#delay? It’s an excellent way to schedule a function to run later— one hundredth of a second later, in this case. Here, incrementBox schedules itself to run later, over and over, until we reach the desired value. We need to call it only once to set the effect in motion. Figure 10-3 shows the “before” and “after” states of the box. Figure 10-3. The element animates into its new position. CHAPTER 10 ■ INTRODUCTION TO SCRIPT.ACULO.US EFFECTS 219 So let’s look at the ingredients of this effect: • The element ( <div id="box">) is the target of the effect. • The starting and ending points are pixel coordinates ( 0, 0 to 50, 50). • The incrementing is done by Element#setStyle. This method works, but not as well as it could. It’s far too dependent on the speed of the computer it runs on, and it’s not guaranteed to move at a constant pace. Our call to Function#delay guarantees that our function will be run at least 10ms later, but if the JavaScript engine is busy with something else, we could be waiting for quite a bit longer. We’re guaranteed to get 50 frames of animation, but the element might not move at a constant speed. script.aculo.us Effects Fortunately, script.aculo.us manages all these annoying details for you. It creates a base class for running effects, and then defines scads and scads of specific effects that inherit from that base class. The effects themselves vary wildly, but they all have several things in common: • They all act on an element. • They all involve transforming that element’s style from a starting point to an end- ing point over a specified amount of time. No matter how slow the computer or how overwhelmed the JS engine, script.aculo.us effects will always execute in the amount of time specified. • They all accept callbacks that let us run our own functions at certain milestones in the animation. These callbacks are very similar to the Ajax callbacks covered in Chapter 4. The previous exercise was meant to give you an idea of how effects work under the hood. script.aculo.us defines a base class that solves all the problems we encountered— a boilerplate for any sort of browser-based effect. It then defines subclasses that do spe- cific things: one animates opacity, another animates position, and so on. These are called core effects. Finally, it defines some functions that build on these core effects by running two or more in parallel. These are called combination effects. CHAPTER 10 ■ INTRODUCTION TO SCRIPT.ACULO.US EFFECTS220 Using Effect.Morph Simple cases first, though—let’s look at Effect.Morph, the most generic (and most versa- tile) of all effects. It works like a time-lapse version of Element#setStyle: it takes an element and some CSS rules to apply to the element, and slowly “morphs” the element according to the new rules (see Figure 10-4). new Effect.Morph('box', { style: "left: 50px; top: 50px;", duration: 1.0 }); Figure 10-4. The Effect.Morph call animates the box into the desired position. CHAPTER 10 ■ INTRODUCTION TO SCRIPT.ACULO.US EFFECTS 221 In other words, from the starting and ending values of each element’s properties, Effect.Morph figures out which CSS properties it needs to change and gradually adjusts them over the specified period of time. Effect.Morph uses the options-argument pattern we’ve come to love. The first param- eter, style, can be either a string or an object in the form expected by Element#setStyle. An optional duration argument indicates how long the effect should last; if absent, it defaults to 0.5 seconds. script.aculo.us also adds Element#morph for maximum convenience. This code exam- ple is equivalent to the preceding one: $('box').morph("left: 50px; top: 50px;", { duration: 1.0 }); Effect.Morph works because a vast majority of CSS properties lend themselves to tweening—figuring what goes in between a starting point and an ending point. Anything with a quantitative value—pixels, ems, hex color values, and so on—can be morphed. $('box').morph("width: 500px"); $('box').morph("font-size: 1.6em"); $('box').morph("background-color: #cc5500"); How Does It Do That? As we’ve established, all effects need to know several things: the element to change, the aspect of that element to change, and starting and ending values. Effect.Morph lets us specify, in very precise form, all of those things except for one—we don’t need to specify a starting value because it’s assumed we want to start at whatever state the element is already in . This shortcut limits the overall expressive power of Effect.Morph, but broadens its applicability. To illustrate this, let’s place our element at a different starting point: #box { position: absolute; width: 50px; height: 50px; top: 200px; left: 100px; background-color: #999; border: 2px solid #000; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; text-transform: uppercase; text-align: center; line-height: 50px; font-size: 10px; } CHAPTER 10 ■ INTRODUCTION TO SCRIPT.ACULO.US EFFECTS222 . a constant speed. script .aculo. us Effects Fortunately, script .aculo. us manages all these annoying details for you. It creates a base class for running effects, and then defines scads and scads of specific. SCRIPT .ACULO. US EFFECTS 221 In other words, from the starting and ending values of each element’s properties, Effect.Morph figures out which CSS properties it needs to change and gradually adjusts them. effects. CHAPTER 10 ■ INTRODUCTION TO SCRIPT .ACULO. US EFFECTS220 Using Effect.Morph Simple cases first, though—let’s look at Effect.Morph, the most generic (and most versa- tile) of all effects. It