ANIMATION (https://www.kirupa.com/css_animations/index.htm) BOOK (http://www.amazon.com/dp/1502548704?_encoding=UTF8&camp=15041&creative=373501&linkCode=as3&tag=kirupacom) Animating with Robert Penner's Easing Functions by kirupa (https://www.kirupa.com/me/index.htm) | filed under Web Animation (https://www.kirupa.com/html5/learn_animation.htm) To borrow something I repeated in the Easing Functions in CSS (easing_functions_css3.htm) tutorial, one of the most important things you can do is introduce your animations to easing functions. Without easing functions, your animations will move in a very boring and mechanical way: Your browser does not support inline frames or is currently configured not to display inline frames. The animations you create should not look like this. Your animations should be relatable and mimic how things move in real life. To help with that, you have this tutorial. In the next bazillion sections, you are going to extend what you've learned in Part I: Introduction to Easing in JavaScript (https://www.kirupa.com/html5/introduction_to_easing_in_javascript.htm) and learn how to use easing functions to make the animations you create in code more awesome. Here, you will no longer be defining your own easing equations. Instead, you will learn how to take pre-defined easing equations and and use them to create an animation that looks as follows: Your browser does not support inline frames or is currently configured not to display inline frames. Notice that this animation doesn't exhibit the boringness of the first animation. The animated circle starts off slow, speeds up in the middle, and slows down towards the end. All of this is made possible thanks to easing functions. Onwards! Robert Penner and Easing Functions Many years ago (some time after the last dinosaurs died but before the SSV Normandy was commissioned) a very smart person known as Robert Penner (http://www.robertpenner.com) started defining easing equations in code and began sharing them with the world: [Image: my history of the world] (https://www.kirupa.com/html5/images/history_of_the_world_72.png) Over a very short period of time, Robert's library of easing functions became very popular. That was true then. It is still true today. His easing functions have been included into popular libraries, converted into various programming languages, and possibly inspired quite a few hit songs. It's hard to talk about easing functions in code without relying on his initial work, so the easing functions you will learn to add in this tutorial are 100% grown by him. This will not only ensure you are being consistent with what everybody is familiar with, it will allow you to focus on what actually matters - creating high quality animations without reinventing the wheel. Setting Up Your Animation In order to use Robert's easing functions, we need to setup our animation in a certain way where his easing functions can be used "out of the box" without any modification required. This isn't as intrusive as it sounds, so let's look at what these changes involve at a very high level before going deeper into the changes themselves. What We Are Gonna Change For a typical JavaScript animation such as the ones you've seen and created that don't contain easing, they basically work as follows: you have your animation loop, you have your requestAnimationFrame function tasked with looping that loop, and you have your animated properties whose values change each time the loop is called. Below is one example of such an animation stolen (shamelessly) from the Animating in Code Using JavaScript (animating_in_code_using_javascript.htm) tutorial: var theThing = document.querySelector("#thing"); var currentPos = 0; var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame; function moveThing() { currentPos += 5; theThing.style.left = currentPos + "px"; if (Math.abs(currentPos) >= 900) { currentPos = -500; } requestAnimationFrame(moveThing); } moveThing(); Most of the code you see here will remain unchanged as part of working with easing functions. The only things that will change are the lines in your animation loop that I've highlighted below: function moveThing() { currentPos += 5; theThing.style.left = currentPos + "px"; if (Math.abs(currentPos) >= 900) { currentPos = -500; } requestAnimationFrame(moveThing); } Everything else from your requestAnimationFrame call to assigning a value to the property you are animating will stay the same. Despite how limited the code changes are, conceptually you will need to think about your animation in more detail than what we've done in the past. These details are important, and the following sections will drive home both the code as well as the conceptual changes. The Example The help with understanding the changes, let's work with a simple example. As with all great examples, this one involves a shape: [Image: the shape] (https://www.kirupa.com/html5/images/the_shape_72.png) What we want to do is animate this shape's horizontal position over a period of 5 seconds where it slides from left to right. To get into specifics, our shape's horizontal position starts off at -100 pixels: [Image: starting point] (https://www.kirupa.com/html5/images/start_72.png) By the end of the animation, our shape's horizontal position is at 500 pixels: [Image: ending point] (https://www.kirupa.com/html5/images/end_72.png) There is more! To make this animation a bit more realistic (and to justify this example being here specifically :P), this property change will not be linear. We want the radius property to ease in for the first half of the animation and then ease out to its final value in the second half: [Image: easing function] (https://www.kirupa.com/html5/images/silhouette_72.png) A visualization of this type of ease, better known as an ease-in-out easing function, will look approximately as follows: [Image: simple easing function again] (https://www.kirupa.com/html5/images/cubic-bezier_3.png) To quickly recap what we specified so far, our animation's important details are: - starting value: -100px - ending value: 500px - duration: 5 seconds - easing function: ease-in-out The way we've defined the animation so far is in very normal, human-understandable terms. Unfortunately, when you cross into the digital realm that JavaScript lives in, we are going to have to translate these values into something our computer can understand. This means changing how some of these values look like so that we can easily work with them in code. Going from Time to Iterations One of the major changes in how we think about animations in code revolves around how time is represented. When you are animating in CSS, the way you define duration in an animation or transition is in the form of seconds. How those seconds translate into the animation you see on the screen is abstracted away and hidden. Good for them. When you are animating in JavaScript, you do not change property values at particular time points. The whole concept of time never directly comes into play. You change your property values with each call of your animation loop which, thanks to requestAnimationFrame, gets called around sixty times a second! Now, calling each time our animation loop changes 1/60th of a second seems a bit awkward. We need a new name for each time our animation loop gets called. While the word "frame" seems like an obvious choice, let's go a bit more generic and use the word, iteration. There are two types of iterations you need to be aware of that will simplify things further - current iteration and total iterations. We'll look at both of these next. Meet the Current Iteration An important part of working with animations is knowing where an animation will be at any given time. To better make sense of this, let's visualize a group of iterations by a vertically dashed line: [Image: beginning position] (https://www.kirupa.com/html5/images/beginning_position_72.png) The above diagram shows where our shape is after a single iteration. As you can see, that is very close to the starting point of our animation. Now, let's let our animation run for a little bit. After a number of iterations have passed, our above diagram will now look as follows with more lines to indicate progress: [Image: the midpoint] (https://www.kirupa.com/html5/images/silhouette_no_text_vertical_lines_midpoint_72.png) With each iteration, your shape gets closer to reaching the end. Now, here is an important (and obvious) detail to always be aware of. The most recent iteration that has been called is our animation's current iteration: [Image: the current iteration] (https://www.kirupa.com/html5/images/current_iteration_midpoint_72.png) The value our current iteration stores is simply a count of how many iterations (aka how many times our animation loop has been called) have elapsed since the animation started. Total Iterations Now, all animations have a point where they end or reach a temporary "end" before looping or doing something else. When that point occurs is determined by the total number of iterations your animation will have, and this number maps to what once used to be our duration. As you saw earlier, to convert from duration to iterations, multiply your duration value by the animation's frame rate of 60. If your animation is 5 seconds long, that means your animation will run for 300 iterations. If your animation is .2 seconds long, it means your animation will run for 12 iterations. The larger this total iterations value, the longer it will take your animation to complete. The smaller this value, the quicker your animation will complete. While that makes sense for durations, let me elaborate a bit on why that is the case when dealing with iterations. Let's say you specify a large value for the total number of iterations your animation will take: [Image: large number of iterations] (https://www.kirupa.com/html5/images/large_total_iterations_72.png) Remember, the rate at which you progress through each iteration doesn't change. No matter how large or small the total number of iterations your animation has, your animation will progress at sixty iterations each second. This means, the more iterations you have, the longer it will take your animation to get there. It's kinda like adding more miles to a trip when your car will only go at 60 mph. You have more distance to cover, and since your car can't go any faster, you just end up driving (or sitting in the passenger seat) longer to compensate for the extra distance. That is why the opposite is true when you have less iterations to go through: [Image: less iterations] (https://www.kirupa.com/html5/images/small_total_iterations_72.png) Your animation still barrels ahead at sixty iterations a second, but you just don't have as far to barrel through as you did earlier when you had a lot of iterations. By keeping track of the current iteration and the total iterations, you can finely control where your animation will be at any given time. You can also use these values to measure progress. The ratio of current iteration to total iteration will give you the percentage of your animation's completion: [Image: progress] (https://www.kirupa.com/html5/images/progress.png) That's it for our look at the no-longer-mysterious iteration values. You'll see them crop up shortly when we look at how all of these changes fit together. Thinking About the Change in Value Another change involves how we keep track of the property values we are animating. The most common way we track those values is by looking at the start and end: [Image: start and ending value] (https://www.kirupa.com/html5/images/start_end_changeinvalue_72.png) For the way Robert Penner's easing functions work, you need to know one more piece extremely easy-to-find piece of information. You need to know how much the animated value changed from the beginning to the end. This is simply found by subtracting the final value from the initial value: [Image: the change in value] (https://www.kirupa.com/html5/images/600pixels_change_72.png) In our example, subtracting the final value of 500 from our initial value of -100 gives you 600. What this change in value signifies is that our shape traveled by 600 pixels from beginning to end to reach its destination. This is a small detail for us to figure out, but it is a giant leap for getting easing functions to work. Recap Now that you learned about some of the changes, let's update our animation's details: - starting value: -100px - ending value: 500px - change in value: 600px - current iteration: 0 - total iterations: 300 - easing function: ease-in-out The duration value has been replaced with current iteration and total iterations. We've added in change in value to symbolize the difference between the final value and the starting value. You'll see why in just a few moments. Meet the Penner Easing Functions Now that you've had a chance to update how you think about creating animations in JavaScript, it's time to bring in the reason for this update in the first place - the famous Penner Easing Equations. These are a collection of easing functions that cover everything from linear eases to more exotic ones involves cubic and trigonometric expressions. Glance Through Them Before you do anything, first take a look at what his easing functions look like. You can find the JavaScript version of them at the following location: https://www.kirupa.com/js/easing.js (https://www.kirupa.com/js/easing.js) If you glance through that file, you'll see a lot of easing functions defined. A lot of what you see will seem like some complicated mathematical expressions...which they are! The thing I want you to note are the arguments each function takes. Let's pick on the easeOutCubic easing function to elaborate this detail: function easeOutCubic(currentIteration, startValue, changeInValue, totalIterations) { return changeInValue * (Math.pow(currentIteration / totalIterations - 1, 3) + 1) + startValue; } Notice the four arguments that this easing function and every other easing function there takes. These four arguments are: - currentIteration - startValue - changeInValue - totalIterations Do these arguments look familiar? They should! They map directly to the changes we discussed at length in the previous section. What these easing functions define is the mathematical form of the timing curves you saw at length in my Easing Functions in CSS3 (easing_functions_css3.htm) tutorial. Each of the easing functions defined in that JavaScript file help you return a value that falls somewhere on the timing curve depending on the easing function you are looking at. The "somewhere on the curve" part is handled by what you pass in for currentIteration and totalIterations. The value returned by the easing function falls within your startValue and the final value which is simply startValue added together with the changeInValue. You'll see all of this coming together shortly when we look at using an easing function in more detail. Walking Through an Example So far, we've looked at our easing functions in isolation and in a very disconnected-from-reality point of view. I haven't concretely explained how all of this would look in a real animation. Let's fix that now in preparation for applying everything you've learned into a real example. Let's say that you have your requestAnimationFrame loop set as follows: function animate() { requestAnimationFrame(animate); } animate(); What you have right now is nothing fancy. You have the bare minimum needed to have your animate function get called sixty times a second by the requestAnimationFrame function. Your easing function will go inside the animate function: function animate() { easeOutCubic(currentIteration, startValue, changeInValue, totalIterations) requestAnimationFrame(animate); } animate(); Besides the arguments currently being non-existent, there is something else that is wrong here. Your easing function isn't supposed to be used in isolation like I've shown. Your easing function actually returns a value. It returns the current value based on what you pass in for the four arguments, so using it as we have now is pointless. Let's address this by creating a variable called currentValue to store the results our easing function returns: var currentValue; function animate() { currentValue = easeOutCubic(currentIteration, startValue, changeInValue, totalIterations) requestAnimationFrame(animate); } animate(); Right now, with this minor change, our easing function is providing some value (literally!) that we can use later. With this issue sorted out, let's shift our attention to the arguments. It's time for another example! In this example, we want to animate the size of some text increasing from 16 pixels to 128 pixels: [Image: hello text] (https://www.kirupa.com/html5/images/text_small_72.png) The value we are animating is the pixel size. The start value for this is 16 pixels. The end value is 128 pixels. This means the change in the value is 128 minus 16...which is 112. We now have the value for our startValue and changeInValue arguments: var currentValue; function animate() { currentValue = easeOutCubic(currentIteration, 16, 112, totalIterations) requestAnimationFrame(animate); } animate(); Next up is filling in the values for currentIteration and totalIterations. We'll start with totalIterations first and then cover the slightly more complicated currentIteration argument. I want this animation to occur over a period of 5 seconds. We know that the animate function gets called 60 times a second. Over 5 seconds, this means our animate function will get called 300 times. Because we've been using the word iteration to mean each time our animation loop gets called, the total number of iterations for our animation over 5 seconds is 300. And just like that, we have the value for totalIterations: var currentValue; function animate() { currentValue = easeOutCubic(currentIteration, 16, 112, 300) requestAnimationFrame(animate); } animate(); The currentIteration argument isn't as easy to set as the three other arguments you've seen so far, but it isn't complicated either. You'll have to write a few lines of code to have it make sense. Remember, your currentIteration value is a count of how many times your animation loop (the animate function in our case) has been called. What we need is a counter that is incremented each time this function is called and specify that counter as what you pass in for currentIteration. All of this would look as follows: var currentValue; var iterationCount = 0; function animate() { currentValue = easeOutCubic(iterationCount, 16, 112, totalIterations) iterationCount++; requestAnimationFrame(animate); } animate(); Notice that I declared a variable called iterationCount, and this variable is what gets passed in to our easeOutCubic easing function as well as what gets incremented by one each time. This is all it takes for your animation to now run. See, the ratio between iterationCount and totalIterations ranges between 0 (the very beginning) and 1 (the very end). The value that gets returned by your easing function uses the ratio between iterationCount and totalIterations as a way of measuring progress. Based on that, the value your easing function returns falls between your start value and your final value (startValue + changeInValue). There are a few more things that are worth calling out such as looping and alternating once your animation hits the end, but we'll cover them as part of looking at a real example. Using an Easing Function (for Real)! By now, you've seen waaay to much background information and high-level examples of what the Penner Easing Functions are and how they work. It's time to put all of this into practice in our own example - the same example of the sliding blue circle you saw at the very beginning: [Image: the position from left to right] (https://www.kirupa.com/html5/images/bca_position_72.png) The circle's horizontal position starts off at -100 pixels and ends at 550 pixels. To put differently, the total change in how far our circle traveled is 650 pixels. While you can't see from the image, the circle makes this journey in around 3 seconds. The rest of this section is about making this example real, so start by creating a new HTML document. In this document, add the following HTML, CSS, and JavaScript into it: