Overview
In this tutorial we'll construct a Timer rig that can be easily controlled using expression control effects.
Note: If you want a ready to use version we put together a script that you can download from our Github page. On launch, it will add a new text layer to your comp and add the expression and the controls for you. To run it, simply create a new composition or open an existing one in AE. Then choose File -> Scripts -> Run Script File...
to launch the script. This script will only work in AE 2020 (17.0) or above - due to the Dropdown Menu control effect.
Quick Reference
Below is the complete expression for quick reference and a walkthrough on how to set up the Timer object. For a code breakdown or to learn how to build it from scratch jump to the Building the Timer Rig section.
Setup
Create a new text layer in a new or existing comp and rename it to Timer
(or whatever you choose).
Next create the following expression controls:
Type | Name | Value | Description |
---|---|---|---|
Checkbox Control | Countdown | On | Toggles count-up or count-down |
Dropdown Menu Control | Time Format | Item 1: HH:MM:SS, Item 2: MM:SS, Item 3: SS | Specifies the display format |
Slider Control | Rate | 1 | Specifies the timer speed. 1 = Real time, 2 = Double time, etc.. |
Slider Control | Clock Start - Hours | 24 | Start Hours |
Slider Control | Clock Start - Minutes | 0 | Start Minutes |
Slider Control | Clock Start - Seconds | 0 | Start Seconds |
Checkbox Control | Milliseconds | On | Toggle Milliseconds display |
Checkbox Control | Negative Time | Off | Toggle minus symbol in front of time |
Finally, paste the expression below in the source text attribute of the text layer.
// get Timer properties
rate = clamp(effect("Rate")("Slider"), 0, 100);
h = effect("Start Time - Hours")("Slider");
m = effect("Start Time - Minutes")("Slider");
s = effect("Start Time - Seconds")("Slider");
c = effect("Countdown")("Checkbox").value;
ms = effect("Milliseconds")("Checkbox").value;
format = effect("Time Format")("Menu").value;
n = effect("Negative Time")("Checkbox").value;
// convert Start Times to seconds and sum.
st = h*3600 + m*60 + s;
// count up or count down based on checkbox value
t = c ? st - rate*(time - inPoint) : st + rate*(time - inPoint);
// initialize time as [HH:MM:SS:MSS]
f = t <= 0 ? [00, 00, 00, 000] : [t/3600, (t%3600)/60, t%60, t.toFixed(3).substr(-3)];
// Round to whole numbers and add zero padding
for (i in f){ f[i] = String(Math.floor(f[i])).padStart(i>2?3:2, "0") }
// remove ms if checkbox is off
if(!ms) f.pop();
// change Timer display based on chosen format
switch (format){
// HH:MM:SS
case 1:
t = f.join(":");
break;
// MM:SS
case 2:
t = f.slice(1).join(":");
break;
// SS
case 3:
t = f.slice(2).join(":");
}
// prepend minus symbol to time if checkbox is on
n ? "-" + t : t;
Building the Timer Rig
We'll be building the rig from scratch so we can understand exactly what's going on. We can then adapt this knowledge to customize our Timer further or to build similar items. Most of the methods used throughout this tutorial will come in very handy when building any other rigs.
Setup
Let's setup our AE project to follow along. Create a new comp Cmd/Ctrl N
. Inside the new comp, create a new Text Layer: Layer -> New -> Text
. Rename it to Timer
.
Basic Timer Functions
Since we'll be working with Time, it's important to understand some basics. In After Effects, you can use the time
method to get the current time in seconds.
Toggle the expression panel for the source text attribute of the text layer - ALT + LMB
(Option/Alt + Left Mouse Button Click) the stopwatch next to the attribute name. Now type time
in the expression box, then click outside it to commit your expression. If you hit spacebar
now you should see the time incrementing, starting from 0
.
Let's change the expression so that only whole seconds are displayed: Math.floor(time)
. Math.floor()
rounds down whatever number is passed inside the brackets to the nearest whole number. In this case it will round down to the nearest second.
Note: Notice the Math
before the floor()
method. Math
is a Javascript object that holds a variety of math methods. Since floor()
is a method that belongs to Math
we have to specify both in that order. For a complete list of supported functions see: Mozilla Developer Network - Math.
This could do for a basic Timer, right? To turn it into a countdown Timer we can simply subtract the time method from a predefined value.
Back to the Simple Timer that counted up. What if we wanted to double the speed? We can simply multiply the time
by 2
.
Now for every second, our Timer will display double the value. Basically count twice as fast. To count twice as slow we can use:
But what if we wanted to change the speed value later on? It requires finding the attribute with the expression, opening the expression box, locating the line with the speed value, changing it, clicking outside the box to commit... takes too long - especially if we have a complex expression or we need to change multiple instances of it. Expression Controls to the rescue!
There are different types of controls that we will cover throughout the tutorial. For now let's create a new Slider Control by heading over to the Effects Panel CMD/CTRL 5
and searching for it in the search box. Double click to apply the effect to the layer.
Note: Make sure to select the Timer layer before Double Clicking on the effect to apply it.
Rename it to Rate
.
Let's go into our expression box once again and at line 1 (before our current expression) we can type rate =
. Then, we can pickwhip to the Rate Slider we just created. Its path should be added to the expression where our cursor is. Our variable rate
is now referencing the Slider value. We then simply swap the speed value with rate
.
Lastly, we may want to add limits to our Slider value. To prevent using a rate that is negative or too high. We can use the clamp()
method so we can specify a min
and max
value. The syntax is: clamp(value, min, max)
.
Note: We could use a negative rate value to have the Timer count down but we'll create a checkbox control for that instead, later on.
Note: We can limit the range of the Slider by right clicking on the Slider Value and choosing Edit Value...
from the menu. This will open a panel where we can adjust the Slider range. However, this does not prevent us from inserting a value outside the range (except for when working with MOGRT templates) - it just limits the display of the range slider (accessed by clicking on the arrow to the left of the Slider value's stopwatch icon).
Let's move on to adding features to our Timer.
Setting a Start Time
An essential feature is the ability to specify a start time. Let's add some new expression controls so we can define this in Hours, Minutes, and Seconds.
Add 3 new Slider controls and rename them to:
- Start Time - Hours
- Start Time - Minutes
- Start Time - Seconds
Now reference them in the Timer source text expression box.
Since time
in AE is returned in seconds, let's convert the Hours and Minutes and sum them up so we have a total time in Seconds.
There are 60
seconds in 1
minute, and 3600
seconds in 1
hour, so we multiply the number of seconds in each unit by their value.
Now let's add the sum to our current time.
If you change the values of any of the three Start Time sliders you'll see the text displaying the sum of those, plus the current time, in seconds.
Count Up or Count Down
Let’s add the option for the Timer to count up or down.
First, add a new checkbox control in the effects panel, and rename it to Countdown
.
Now reference it in our expression.
Note: By default, referencing the checkbox returns a value of type String. We need a Number in our next step so we need to add value
at the end of the expression to retrieve the value as a Number. Whenever we need to convert a String to a Number we can also use this method: Number(string)
, or String(Number)
for the inverse.
If we type c
at the bottom of our expression and click outside of the expression box to commit changes, we'll see that the value shown is either a 0
or a 1
- based on whether the checkbox is toggled on
or off
. 0
and 1
represent in fact off
and on
respectively. But they also represent false
and true
. We can use those values in a conditional statement to check whether a condition is true or false.
Conditional Statements
If you are unfamiliar with conditional statements in programming, here is an example:
It literally means if
the statement in the brackets ()
is true
, run some code, if false
, run some other code.
We want to check whether our checkbox is on | 1
| true
or off | 0
| false
so we can simply pass it as a variable inside the brackets.
If true
, we want to return the Start Time - the Rate to count down, and if 'false' we want to add the Rate. Paste the following code at the end of your expression and then try toggling the Countdown checkbox control.
Shorthand Conditional Statements
There is a shorter way of writing a conditional statement, using Conditional Ternary Operators and we can assign the outcome directly to the t
variable.
Finally, let's have the Timer start at the layer's in point, so we can move the layer around in the Timeline to define when the Timer will start counting. We can access a layer's in point using layer.inPoint
and simply inPoint
if we are referring to the current layer.
Here is the snippet in context.
Let's move on to formatting, so we can control the way the time is displayed.
Formatting Time
We have the Timer working but it returns the time in seconds. You may want to control how the time is displayed, in a clearer way. Let's format the time so it is returned in Hours, Minutes, Seconds and Milliseconds HH:MM:SS:MSS
.
Converting Seconds into Hours and Minutes
First we need to convert the seconds into Hours and Minutes. But in order to have the timer behave like a clock, we need to ensure seconds and minutes restart at 0
once they reach 60
. We can use the Modulus operator to do this: %
.
In essence, the Modulus operator allows us to find the remainder of a division. An example is if we want to find the remainder of 10 / 4
. The closest integer to 10
that is less than 10
and divisible by 4
is 8 (8 / 4 = 2)
. If we subtract the product from our dividend we get the remainder: 10 - 8 = 2
.
Note: A more detailed explanation of the modulus operator is featured here: The Modulus Operator in Detail.
So the operations, in order, are (with t
being the total seconds):
- Hours:
t/3600
- basically dividing by60
twice. - Minutes:
(t%3600)/60
- If the total seconds exceed3600 (the hour)
, the remainder is returned, that is then divided by60
to give us the minutes. At3600
the remainder is0
. - Seconds:
t%60
- If the total seconds exceed60 (the minute)
, the remainder is returned. At60
the remainder is0
. - Milliseconds:
t.toFixed(3).substr(-3)
- Without rounding, the currentt
value has numerous digits after the decimal point. To extract the first 3 digits after the decimal point, we can use a combination of methods. First we can round to 3 decimal places usingtoFixed(3)
, then we can cut everything else but those 3 decimal places usingsubstr(-3)
.
Note: The Substring method substr()
is used to cut characters from a string based on their position. Using substr(3)
would cut everything after the third character starting from the beginning of the string. Using substr(-3)
performs the same cut, but starting from the end of the string.
Using an Array to Store the Conversions
Let's store these conversions in a single variable (f
), by using an Array.
Finally, let's use a Conditional Ternary Operator once more. This time, to check if the relative time falls below zero. We'll prevent the Timer from displaying negative values since we'll add the ability to toggle between a positive or negative time display later on - with a Checkbox control.
If time
is less than or equal to 0, cap all values to 0 (with padding). Else, store the time conversion operations we defined earlier. Paste the following snippet at the bottom of the expression.
Note: Above we used the comparison operator <=
, to check if a value is less than or equal to. Here you can find a complete list of operators.
If you press play now, you will notice a bunch of rather long numbers separated by commas. We still need to round those numbers down! (except for Milliseconds since we dealt with those earlier using toFixed()
and substr()
).
Rounding Numbers Down
For rounding down we'll use the Math.floor()
method as discussed previously in the Basic Timer Functions section.
We could write something like this:
...but let's avoid being repetitive - we can instead use a for loop.
Looping through Array Items
Paste this snippet at the bottom of your expression. We now have our numbers nicely rounded to whole numbers.
How it works is rather straightforward. f
is our array: [HH,MM,SS,MSS]
. The for loop will run the code specified within the brackets for each item i
in the array. Where i
is the index of the current item, starting from 0
. In our case we have an array of length 4
([0, 1, 2, 3]
), so the loop will run 4
times. In our example, the code run by the for loop retrieves the item value and rounds it down, then stores it back in place within the array.
Join Array Items
The output we see in the comp viewer is displaying the time in the Array format. Let's use the join()
method to join all items in the array into a String. We can specify how to join the items and, in our case, we'll use the colon symbol :
, but you can specify any character sequence. This will convert [00, 00, 00, 000]
to 00:00:00:000
.
Note: join()
expects a string within the brackets, so any characters you input need to be specified within quote marks " "
or ' '
. Check out the join()
method reference on MDN.
Zero Padding
When we perform our Time conversions (e.g. from Seconds to Hours) and we round down, any result between 0-9
will be displayed as a single digit. We can add padding to the left of those digits so that: 9
will turn into 09
. In Javascript there is a method that adds padding to the start of a String: padStart()
.
The padStart()
method pads the current string with another string (multiple times, if needed) until the resulting string reaches the given length. The padding is applied from the start of the current string. — MDN Web Docs.
Note: There is also a padEnd()
method: MDN Web Docs - padEnd().
Since padStart()
requires a String we have to first convert our Numbers to Strings. Then we can use the padStart()
method by specifying the length that the new String should be, and by what character it needs to be padded by. For our purpose we'll specify a length of 2
and the "0"
character (quoted, as we are working with Strings, remember!).
We need to apply this to all our items in our Time array. Let's include this method in the for loop we setup earlier (appended to the rounding method Math.floor()
).
If we run this at a Time of [1, 1, 1, 1]
we would end up with [01, 01, 01, 01]
. But we are catering to this format: [HH,MM,SS,MSS]
- so our last item should have a length of 3
: [01, 01, 01, **001**]
. Here we can use a simple [#shorthand-conditional-statements](Shorthand conditional statement) to check whether the current item in the loop is the last item (or greater than 2
considering the item positions in the array: [0, 1, 2, **3**]
, and run the padStart()
with a length of 3
instead of 2
: padStart(i>2?3:2, "0")
. Replace your for loop with the following snippet.
Note: To see the output, simply type the variable name you want to see at the bottom of the expression. In this case type f
.
Toggling Milliseconds Visibility
We may want to toggle on
or off
the Milliseconds display. Let's add a new Checkbox control to our Timer layer.
Rename it to Milliseconds
, then reference it in the expression.
For the toggle to work we are going to use a method named pop()
on our array. This will remove the last item of the array and, conveniently, our Milliseconds item is the last item of the array.
Let's use a conditional statement to check whether the toggle is off, so we can pop the Milliseconds from the array. (shorthand is not needed this time since we are not providing a fallback - nothing needs to happen if the toggle is on). Paste this snipper below our for loop.
Notice the exclamation !
symbol. This is known as a Logical NOT or Negation and it simply inverts a Boolean value. Basically true
becomes false
and vice versa.
In our case we are going to use it since we are checking whether the checkbox is off
, to remove the Milliseconds.
Note: Expressions are run once on every time change, but also if a value referenced in the expression changes - our checkbox control for example. Therefore we don't need to specify that the Milliseconds should be added back in if the checkbox is toggled back to off
, since the entire expression will be re-evaluated anyways as soon as the value of the checkbox is changed.
Time Format Dropdown Menu
In this section we are going to add the ability to choose between 3 time formats using the Dropdown Menu control.
Note: The dropdown menu control is a new feature in The After Effects 2020 (17.0) version. If you are running a previous version you may want to use a Checkbox control for each format to toggle between them.
Add a new Dropdown Menu control to the Timer layer. Rename it to Time Format
and reference it in the expression.
Note: Just as for a Checkbox Control we want to retrieve the value as a Number so we need to add the .value
at the end.
Now head over to the Edit... button next to the new dropdown we created and edit the values to have:
HH:MM:SS
MM:SS
SS
Once set, we can start writing the logic for our control. We could easily use a conditional statement to check which option is selected in the dropdown.
But let's use another way to achieve the same thing, that may be better suited for checking multiple cases: using a Switch Statement.
Switch Statements
Let's look at the structure of a Switch Statement first.
The switch statement evaluates an expression, matching the expression's value to a case clause, and executes statements associated with that case, as well as statements in cases that follow the matching case. — MDN Web Docs.
Note: Notice the break;
statement within each case. This is optional and prevents the case below it to be executed. So if we omitted break
from case 1
, and the dropdown option was 1, case 2 would also run. The process would then stop since it encounters the break
in case 2
. With that in mind, we could actually omit the break
in case 3
since there are no cases afterwords but we'll leave it in - we may want to add a new case later on.
With the switch statement skeleton setup we can start adding the commands to format the time array. We'll use a similar method of removing items from the array, just like we did with the Milliseconds where we popped the value at the end of the array. We'll then use the join()
method discussed earlier to combine the items of our array and output a formatted string: HH:MM:SS
.
Slicing and Joining Arrays
Currently, when the expression runs, 4 items are added to our time array: [HH, MM, SS, MSS]
. To select portions of the array, say we wanted to only have [SS, MSS]
, we can use the slice()
method. This method allows us to slice an array based on a start and end range, retrieving the items within the range.
Let's implement slice()
and join()
for each case. Remember that the Milliseconds are handled by our checkbox control so we don't have to deal with that here.
Case 1 - HH:MM:SS
Here we just need to join the array since we are displaying all the items.
Case 2 - MM:SS
To retrieve all but the first item of the array we can pass 1
as the start range value. No need to specify an end range since slice()
, by default, retrieves the values all the way to the end of the array. Remember that array positions start at 0
.
Case 3 - SS
So to retrieve all items but the first 2 we simply pass 2
as the start range.
The Completed Switch Statement
Paste the following snippet at the bottom of the expression. Make sure there is a break
statement at the bottom of each case, and we are good to go.
Negative Time
To conclude our Timer rig, let's add another Checkbox control and rename it to Negative Time
. Reference it in the expression.
We'll use this to set whether the time displayed is positive or negative. All we are doing is prepending a minus
sign before the time string t
.
Conclusion
Here is the final expression.
This concludes the tutorial! Hopefully you now have a fully functioning Timer rig and a solid understanding of how it was built.