Contents
Introduction
In the first article of this series, After Effects Basics, I briefly mentioned the text animator. It is a tool in After Effects, which allows for animation of independent characters, words, or lines, as opposed to the layer controls, which affect the entire text layer as a whole.
However it isn't the most intuitive to use. Therefore I wanted to go over it again in depth, starting with basic concepts, all the way to advanced techniques.
Let's get started.
Using The Range Selector
From the properties panel, click the +Add Animator
button. Select the property you want to animate. For this example, I will be using position
.
When you select a property, the layer will have new options open up in the timeline, like so:
You'll see the new property, Animator 1
added. Inside of it, there is Range Selector 1
, and a new position stopwatch. This is the basic setup for the text animator.
Change the y coordinate of your position to 100
. You'll notice the text shift down 100 pixels. Next, open up the Range Selector property. You'll see start
, end
and offset
parameters (as well as some advanced options). To demonstrate how the range selector works, make a keyframe at the beginning of your timeline for the start
parameter, setting it to 0
. Then, at 1 second, add another keyframe setting it to 100
. As you play this back, you'll see the letters of your text layer animate, one by one, raising back to their default position.
As the name suggests, the start and end parameters of the range selector control how much of the text is affected by all the properties listed inside Animator 1 (in this case, position). With the start set to 0
, and the end set to 100
, the entire range is selected, so all characters will be affected. However, as we change the start from 0
to 100
over time, it stops affecting the text gradually as the range gets smaller, resulting in none of the text being affected once it reaches 100
.
You can also create a similar animation using the offset parameter, instead of the start or end. This allows you to keep your range fixed, but offsets the values from -100
to 100
. This allows for a bit more flexibility, and to easily reverse the animation.
Finally, if you explore the advanced options of the range selector, you can find some options to help fine tune the animation. You can change whether or not the range selects the text by character
, word
or line
. Perfect if you have a long sentence or heading. You can also affect the easing of the animation, randomise the order characters are affected, and change the shape of the animation. This will alter how the text transitions, and is worth playing with to achieve different results.
You can also apply multiple Animators to the same text layer with slightly offset keyframes, to achieve some fun effects.
To try this, remove all keyframes currently on your timeline. Set start to 0
, end to 100
, and animate offset over 1 second, from -100
to 0
. Then, duplicate Animator 1. This will create an Animator 2
property. Open this up, and set the position inside Animator 2 to -50
. Select the keyframes for Animator 2's range selector, and move them forward 4 frames. You'll notice this creates a small bounce back for your text.
If you want to add additional properties to the same Animator, you can do so by clicking on the add
button next to the Animator in the timeline, selecting property
, then whatever property you would like to add.
And that covers the range selector. For some projects, this is enough. Stop here for a bit and have a play with this selector. Try using different properties within the same Animator, or create multiple Animators with different effects. Once you're happy you understand how this control works, you can move onto the next step: working with the expression selector.
Using The Expression Selector
The range selector is not perfect. It does not allow us to control the delay between each letter as it animates, but instead limits us to changing the shape of the animation, the offset, and ease. This means that animations can sometimes be hard to time, and often, does not allow for a smooth transition of letters. What if you want to start the animation of the second letter before the first has completely finished?
If you need to be more precise, you will need to use the expression selector.
It took me some time to work out how to use this selector. But now that I have, I much prefer it to the range selector.
First, create a new text layer, add an Animator with a position property like before, and set the y coordinate to 200
. This time however, delete the range selector. Then from your timeline, click the add
button next to Animator 1. Select Selector
/Expression
. Opening the property up, you will see it has a lot less options than the range selector. The Based On
option allows you to change whether the selector affects characters, words, or lines. Other than this, there is simply an Amount
property, which changes how much the Animator applies to the text. By default, there is an expression written in the amount parameter: selectorValue * textIndex/textTotal
, and your text layer will look something like this:
This immediately throws 3 variables at us without explanation. Allow me to go through these now, and why they are affecting the text in this way.
selectorValue
is referencing the Amount
value of this property. By default, it is set to 100, 100, 100
.
textIndex
assigns an index number to each character, word or line in your text layer (depending on what your based on
property is set to), so that each may be affected individually.
textTotal
is the total number of characters, words, or lines in your text layer (depending on what your based on
property is set to).
With my based on
set to Characters
, we can now make sense of the expression written in the amount property.
textIndex/textTotal
will produce a number between 0 and 1. This in turn is multiplied against the selectorValue
. Therefore, the first character in the text layer with an index of 1, will be affected by the Animator only a small fraction. However, as textIndex
increases in value, the amount the Animator affects the text also increases. This goes on until we reach the last character of the text layer, which is 100% affected by the effects of the Animator.
Another way to understand it, is to see each letter affected in sequence.
Let's replace the default expression with this one:
if (textIndex == Math.floor(time)) 100
else 0
Create an if
statement. If the textIndex
is the same as Math.floor(time)
, the Animator will be applied to it 100% (Math.floor
is applied to ensure the statement only returns integers). Else, it will be applied at a value of 0 (meaning it will have no effect whatsoever).
As you scrub through the timeline, you will see one by one the letters of your text move.
This is how we select our range in the expression selector. This may be a bit confusing at first. But hopefully, I can help demystify the process of writing expressions for dynamic text movement a little bit.
So, we now know textIndex
is one of the values we need to consider when writing the expression. Let's now consider the other values we will need.
Go ahead and change the y position property within Animator 1 to -200
. This will help us write the expression to come.
Delete the expression from your expression selector, and create the following variables:
var frames = 1 / thisComp.frameDuration; //comp frame rate
var delay = 1; //frame delay between each character, in frames
var dur = 6; //duration of animation per character, in frames
frames
is simply the framerate of the current composition. We will need this in order to convert our other values into frames rather than seconds, since working in seconds can be confusing when needing such small increments. This is easily set by dividing 1
by thisComp.frameDuration
.
delay
is the time between each character in our animation. The lower the number, the less of a delay, therefore the quicker the animation. Setting it to 1
will result in a 1 frame delay between each character - meaning the second letter will start to animate 1 frame after the first letter starts to move, and so on.
dur
is the duration of the animation, per character. I've set mine to 6 for a medium speed animation.
Now we have established our variables, we can create an ease
function to animate the text. This will provide a gradual change in value to our characters, vs our previous if
statement.
Let's take a look at this:
var frames = 1 / thisComp.frameDuration;
var delay = 1;
var dur = 6;
var indexDelay = textIndex*(delay/frames);
var frameDur = dur/frames;
ease(time, indexDelay, indexDelay + frameDur, 100, 0);
As you can see, we start off again with our variables frames
, delay
, and dur
. But there are two more variables we need to create in order to connect them with the textIndex
value, and keep the ease
function easy to read.
First is indexDelay
. This simply multiplies the textIndex
with our delay
variable (divided by frames
, to convert the delay into the correct time increments). This will dictate when each letter begins its animation.
The second is frameDur
. This simply divides dur
by frames
, to convert the animation duration into the correct time increment.
Finally, the ease
function. If you are unfamiliar with the ease
function, I go into it in depth in this article. I highly recommend making yourself familiar with how this function works before continuing.
I set the first argument to time
, so the selector value is remapped to time
and will change as the seconds tick by. Next, I set my in and out points to indexDelay
, and indexDelay + frameDur
. This means each character will animate in turn from its own index specific start point, for as long as our dur
value, in frames. Finally, the last 2 arguments will set what values the selector property animates between. These are set to 100
and 0
respectively, meaning the Animator will go from 100% affecting the text, to 0% affecting it, returning the text to its default position.
The result of this expression is this:
Already, we can see a big difference in the timings here vs the range selector. Here, we are able to play with the timings in a much more precise way. Feel free to adjust the numbers of the delay
and dur
variables, to see what can be achieved.
Like the range selector, you can duplicate Animator 1 to create an Animator 2
. By simply adjusting time
in Animator 2's ease
function, you can create a similar bounce effect as we did for the range selector before:
Animator 2 expression
var frames = 1 / thisComp.frameDuration;
var delay = 1;
var dur = 6;
var indexDelay = textIndex*(delay/frames);
var frameDur = dur/frames;
var inTime = (thisComp.frameDuration * 4);
ease(time - inTime, indexDelay, indexDelay + frameDur, 100, 0);
Result with 2 expression selector animators:
I created the variable inTime
to store my 4 frame calculation, to keep the ease
function tidy.
For the cherry on top, try adding an opacity property to Animator 1, so the text also fades in as it drops down (remember: you can do this by clicking the add
button next to Animator 1, selecting Property
/Opacity
, and setting it to 0
):
Finally, if you want the text to animate on and off, you need only use an if
statement to toggle between 2 different ease
functions on both your Animators:
Animator 1 expression
var frames = 1 / thisComp.frameDuration;
var delay = 1;
var dur = 6;
var indexDelay = textIndex*(delay/frames);
var frameDur = dur/frames;
var timeOut = linear(time, 2, 4, 0, 2);
var aniIn = ease(time, indexDelay, indexDelay + frameDur, 100, 0);
var aniOut = ease(time - 2, indexDelay, indexDelay + frameDur, 0, 100);
if (time < 2) aniIn
else aniOut
Animator 2 expression
var frames = 1 / thisComp.frameDuration;
var delay = 1;
var dur = 6;
var indexDelay = textIndex*(delay/frames);
var frameDur = dur/frames;
var inTime = (thisComp.frameDuration * 4);
var aniIn = ease(time - inTime, indexDelay, indexDelay + frameDur, 100, 0);
var aniOut = ease(time - inTime - 2, indexDelay, indexDelay + frameDur, 0, 100);
if (time < 2 - (thisComp.frameDuration * 4)) aniIn
else aniOut
Result:
I advise you to stop and have a play with this now, to see what you can make before moving onto the advanced techniques.
Advanced Techniques
So, the range selector lets us fine tune our movements, but not control the delay between our letters. Whereas using the expression selector, we can control the delay, but the default linear
and ease
functions leave us with only two types of interpolation for our animation.
Or, does it?
What if we want more dynamic animation?
Then we need to create our own custom easing functions. Or, we can use functions others have already created, such as Penner's Easing functions.
I have gone into depth about these functions in this article before. In short, they are a collection of equations which, in Penner's own terms, add "flavour to motion." And, thanks to these functions being open source, we can use them in our After Effects templates, or make our own .jsx files to store them in free of charge (click on the previous article for a full rundown of how to do this). This saves us massive amounts of time - so thank you very much once again, Robert Penner.
Importing my .jsx file to my project, I make a new text layer and add an expression selector to it like before. Then, I start by referencing my .jsx file:
const penner = footage("Penner Easing Functions.jsx").sourceData.getFunctions();
If you are not looking to make a .jsx file, but instead are adding custom functions manually to your project each time, I will post the details of the functions I will be using here. My first example will be using the easeInOutSine
function, which you can create by adding...
function easeInOutSine (t, b, c, d) {
return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
};
...to the start of your expression, instead of calling a .jsx file.
By using the easeInOutSine
function, we can make a constantly moving text layer, bobbing up and down following a sine curve.
After calling our .jsx file, or establishing the function, we set up our variables, the same as we did before:
var frames = 1 / thisComp.frameDuration;
var delay = 2;
var dur = 12;
var indexDelay = textIndex * (delay/frames);
var frameDur = dur/frames;
Then, once we are happy, we can write our final expression. Since the Penner Easing functions work slightly differently to the default ease
and linear
expressions, our variables will be inputted in a slightly different order:
easeInOutSine(time - indexDelay, 100, -100, frameDur)
Using easeInOutSine
, the indexDelay
is subtracted from time, our first argument, since this function does not establish an in and out point for value remapping. The second argument is the starting value of our selector, which is set to 100
. The third argument is the change in value, so this is set to -100
, to bring the selector value to 0. The last argument is the duration of the animation. This is simply set to frameDur
.
Wow - what a cool trick! Even though we specified a duration of frameDur
, the text will move continuously, as we have not set any boundaries for the custom function to stop its calculations (i'll be getting to this shortly).
By adjusting the delay, we can control the space between each letter like so:
Delay of 3
Already, the effect can be varied substantially, just by changing the delay
value. But the variations don't have to stop there. What if instead of subtracting the textIndex
from time
, we added it to our dur
value? This would create an animation, where every letter of our text moved at a slightly different speed, depending on its textIndex
value.
var frames = 1 / thisComp.frameDuration;
var dur = 12;
var durAlt = (textIndex + dur) / frames;
//Return
easeInOutSine(time, 100, -100, durAlt)
Since we are no longer referencing delay, we can remove this variable and marvel at the results.
This animation was actually the result of a mistake I made testing these expressions. But I really like it! I think it's a great way to add randomness to your work, as it is forever evolving. It is also a testament to the importance of experimentation. I never would have found this possibility if I didn't give myself room to explore and make mistakes.
So, to recap the full advanced expression we've worked with so far:
easeInOutSine
full expression with .jsx file
const penner = footage("Penner Easing Functions.jsx").sourceData.getFunctions();
var frames = 1 / thisComp.frameDuration;
var delay = 2;
var dur = 12;
var indexDelay = textIndex * (delay/frames);
var frameDur = dur/frames;
var durAlt = (textIndex + dur) / frames;
//Return
penner.easeInOutSine(time - indexDelay, 100, -100, frameDur) //Evenly timed expression
//or
penner.easeInOutSine(time, 100, -100, durAlt) //Each character animating at different speeds
easeInOutSine
full expression without .jsx file
function easeInOutSine (t, b, c, d) {
return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
};
var frames = 1 / thisComp.frameDuration;
var delay = 2;
var dur = 12;
var indexDelay = textIndex * (delay/frames);
var frameDur = dur/frames;
var durAlt = (textIndex + dur) / frames;
//Return
easeInOutSine(time - indexDelay, 100, -100, frameDur) //Evenly timed expression
//or
easeInOutSine(time, 100, -100, durAlt) //Each character animating at different speeds
This is all well and good for animating with a sine, but having values go out of bounds may not be as useful for other custom functions. Let's take the easeInOutBack
function for example:
function easeInOutBack (t, b, c, d, s) {
if (s == undefined) s = 1.70158;
if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
};
If we use the previously devised expression for this function:
easeInOutBack(time - indexDelay, 100, -100, frameDur)
Here, we have a problem. The animation is only supposed to drop down once. But, because the function is remapping to time, it continues moving after the specified duration and drops down twice - moving the selector value and going out of bounds. This is because we haven't set any boundaries on our time
. Even after our specified duration, time continues to tick, giving the function space to continue its calculations.
In order to fix this, we need to clamp
the time parameter. Let's make another variable to store this new value in:
var inTime = clamp(time - indexDelay, 0, frameDur);
Here, we clamp
time
, preventing it from counting higher than the frameDur
variable. However, because we are subtracting the indexDelay
from time
first, each character will hit this limitation in turn, allowing each letter to complete its animation before coming to a halt.
easeInOutBack
full expression with .jsx file
const penner = footage("Penner Easing Functions.jsx").sourceData.getFunctions();
var frames = 1 / thisComp.frameDuration;
var delay = 2;
var dur = 12;
var indexDelay = textIndex * (delay/frames);
var frameDur = dur/frames;
var inTime = clamp(time - indexDelay, 0, frameDur);
//Return
penner.easeInOutBack(inTime, 100, -100, frameDur)
easeInOutBack
full expression without .jsx file
function easeInOutBack (t, b, c, d, s) {
if (s == undefined) s = 1.70158;
if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
};
var frames = 1 / thisComp.frameDuration;
var delay = 2;
var dur = 12;
var indexDelay = textIndex * (delay/frames);
var frameDur = dur/frames;
var inTime = clamp(time - indexDelay, 0, frameDur);
//Return
easeInOutBack(inTime, 100, -100, frameDur)
Nice! Using the easeInOutBack
function, we can make a text animation with a bounce, using only one 1 Animator. Much more simple than creating 2 and offsetting the time like before!
You can also control how "bouncy" this expression is by adding the 5th argument to this custom function: s
. According to the expression, while undefined it is 1.70158
. So if we change it to a much larger value (and increase our dur
value to account for the extra animation time), we can make the movement back more pronounced:
easeInOutBack(inTime, 100, -100, frameDur, 5)
There's one last thing to fix with the easeInOutBack
function before we continue. The expression is supposed to ease back at the start and the end of the movement (hence ease in and out) - however currently it only moves back at the end. This is because at the start, our selector is already at max value: 100
. If we want to see the move back at the start, we need to leave room for the selector to move back to. Therefore, I should set my starting value to 90
instead.
easeInOutBack(inTime, 90, -90, frameDur)
Perfect! Now that I have fixed everything for the function to run correctly, I can use it to animate in and out by creating a new aniOut
variable, and an if
statement like before:
easeInOutBack
full expression with .jsx file
const penner = footage("Penner Easing Functions.jsx").sourceData.getFunctions();
var frames = 1 / thisComp.frameDuration;
var delay = 2;
var dur = 10;
var indexDelay = textIndex * (delay/frames);
var frameDur = dur/frames;
var inTime = clamp(time - indexDelay, 0, frameDur);
var outTime = clamp(time - 1.5 - indexDelay, 0, frameDur);
//Functions
var aniIn = penner.easeInOutBack(inTime, 90, -90, frameDur)
var aniOut = penner.easeInOutBack(outTime, 0, 90, frameDur)
if (time < 1.5) aniIn
else aniOut
easeInOutBack
full expression without .jsx file
function easeInOutBack (t, b, c, d, s) {
if (s == undefined) s = 1.70158;
if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
};
var frames = 1 / thisComp.frameDuration;
var delay = 2;
var dur = 10;
var indexDelay = textIndex * (delay/frames);
var frameDur = dur/frames;
var inTime = clamp(time - indexDelay, 0, frameDur);
var outTime = clamp(time - 1.5 - indexDelay, 0, frameDur);
//Functions
var aniIn = easeInOutBack(inTime, 90, -90, frameDur)
var aniOut = easeInOutBack(outTime, 0, 90, frameDur)
if (time < 1.5) aniIn
else aniOut
And with that, we have a good foundation to begin experimenting with Penner's other custom easing functions.
Try using other functions, other properties, and seeing how the delay
and dur
values can alter the effects. Get creative - and if you've made it this far, congratulations! You are now a master of the After Effects text animator.
Conclusion
All examples covered here in this tutorial are achieved using predominantly 1 property: position (with a little helping hand from opacity), only 2 of Penner's Easing functions, and the range or expression selectors. There is still a lot of room for experimentation - for instance, combining the use of range selectors and expression selectors.
As I said before, the text animator tool is powerful. It can create so many unique and incredible animations, and the best part about using expression selectors is that they leave the text layers keyframe-less, and editable - perfect for creating dynamic templates.
I hope this article has been inspiring, and given you the tools to play with your own animations.
As always, do you have any questions? Perhaps you know of a better way to achieve this? Let me know in the comments. I'd also love to see what you create!
Top comments (0)