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.

Running the Timer script in AE.

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
Expression controls applied to the Timer layer.

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;
Full featured Countdown Timer rig for Adobe After Effects. More info at https://gfxhacks.com/timer-rig-in-after-effects-using-expressions
Full featured Countdown Timer rig for Adobe After Effects. More info at https://gfxhacks.com/timer-rig-in-after-effects-using-expressions - timer_rig.jsx
Timer Rig script.

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.

Math.floor(time);
Simple Timer.

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.

10 - Math.floor(time);
Simple Countdown Timer.

Back to the Simple Timer that counted up. What if we wanted to double the speed? We can simply multiply the time by 2.

Math.floor(time * 2);
Count twice as fast.

Now for every second, our Timer will display double the value. Basically count twice as fast. To count twice as slow we can use:

Math.floor(time * 0.5);
Count twice as slow.

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.

rate = effect("Rate")("Slider");
Math.floor(time * rate);
Referencing the Rate Slider.
Control the Timer speed with the Rate Slider control.

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.

rate = clamp(effect("Rate")("Slider"), 0, 100);
Math.floor(time * rate);
Limiting the Slider range.

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:

  1. Start Time - Hours
  2. Start Time - Minutes
  3. Start Time - Seconds

Now reference them in the Timer source text expression box.

h = effect("Start Time - Hours")("Slider");
m = effect("Start Time - Minutes")("Slider");
s = effect("Start Time - Seconds")("Slider");
Referencing the Start Time Sliders.

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.

st = h*3600 + m*60 + s;
Converting time to seconds.

Now let's add the sum to our current time.

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");

st = h*3600 + m*60 + s;

Math.floor(st + time * rate);
Timer begins at predefined Start 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.

c = effect("Countdown")("Checkbox").value;
Referencing the Countdown checkbox control.

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:

if (true) {
	// do something
} else {
	// do something else
}
Conditional Statement structure.

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 (c) {
	// c is 1 | True
} else {
	// c is 0 | False
}
Conditional Statement example.

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.

if (c) {
	st - rate
} else {
	st + rate
}
Conditional Statement for counting up or down.

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.

// t = (condition) ? True, do something... : False, do something else...;
t = c ? st - rate : st + rate;
Shorthand conditional implementation.

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.

t = c ? st - rate*(time - inPoint) : st + rate*(time - inPoint);
Timer starts at layer's inPoint.

Here is the snippet in context.

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;

st = h*3600 + m*60 + s;

t = c ? st - rate*(time - inPoint) : st + rate*(time - inPoint);
Timer with countdown feature.

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):

  1. Hours: t/3600 - basically dividing by 60 twice.
  2. Minutes: (t%3600)/60 - If the total seconds exceed 3600 (the hour), the remainder is returned, that is then divided by 60 to give us the minutes. At 3600 the remainder is 0.
  3. Seconds: t%60 - If the total seconds exceed 60 (the minute), the remainder is returned. At 60 the remainder is 0.
  4. Milliseconds: t.toFixed(3).substr(-3) - Without rounding, the current t 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 using toFixed(3), then we can cut everything else but those 3 decimal places using substr(-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.

f = [t/3600, (t%3600)/60, t%60, t.toFixed(3).substr(-3)];
Storing conversions into 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.

f = t <= 0 ? [00, 00, 00, 000] : [t/3600, (t%3600)/60, t%60, t.toFixed(3).substr(-3)];
Capping negative values at zero.

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:

f = t <= 0 ? [00, 00, 00, 000] : [Math.floor(t/3600), Math.floor((t%3600)/60), Math.floor(t%60), t.toFixed(3).substr(-3)];
Rounding each item in the array.

...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.

for (i in f){
	f[i] = Math.floor(f[i]);
}
Using a for loop to round each item in the array.

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.

f.join(":");
Using join() to combine all elements in the array.

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!).

String(9).padStart(2, "0");
// returns: 09
Example: Padding 9 to become 09 using padStart().

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()).

for (i in f){
	f[i] = String(Math.floor(f[i])).padStart(2, "0")
}
Padding each item in the array.

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.

for (i in f){
	f[i] = String(Math.floor(f[i])).padStart(i>2?3:2, "0")
}
Padding Milliseconds with the correct length.

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.

ms = effect("Milliseconds")("Checkbox").value;
Referencing the Milliseconds checkbox control.

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.

if(!ms) f.pop();
Popping the Milliseconds if the checkbox is off.

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.

format = effect("Time Format")("Menu").value;
Referencing the Time Format dropdown menu control.

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:

  1. HH:MM:SS
  2. MM:SS
  3. 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.

if(format === 1){
    // HH:MM:SS
}else if(format === 2){
    // MM:SS
}else if(format === 3){
    // SS
}
Conditional statement for the format options.

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.
switch (format){
    case 1:
        // HH:MM:SS
        break;
    case 2:
        // MM:SS
        break;
    case 3:
        // SS
        break;
}
Switch statement structure.

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 1:
        t = f.join(":");
t = HH:MM:SS
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 1:
        t = f.slice(1).join(":");
t = MM:SS
Case 3 - SS

So to retrieve all items but the first 2 we simply pass 2 as the start range.

case 1:
        t = f.slice(2).join(":");
t = SS

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.

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(":");
}
Switch statement for the Time format options.

Negative Time

To conclude our Timer rig, let's add another Checkbox control and rename it to Negative Time. Reference it in the expression.

n = effect("Negative Time")("Checkbox").value;
Referencing the Negative Time checkbox control.

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.

n ? "-" + t : t;
Prepending the minus sign to the current time.

Conclusion

Here is the final expression.

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;

st = h*3600 + m*60 + s;

t = c ? st - rate*(time - inPoint) : st + rate*(time - inPoint);

f = t <= 0 ? [00, 00, 00, 000] : [t/3600, (t%3600)/60, t%60, t.toFixed(3).substr(-3)];

for (i in f){
	f[i] = String(Math.floor(f[i])).padStart(i>2?3:2, "0")
}

if(!ms) f.pop();

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(":");
}

n ? "-" + t : t;
Completed Timer Rig expression.

This concludes the tutorial! Hopefully you now have a fully functioning Timer rig and a solid understanding of how it was built.


Additional Resources

Create Motion Graphics templates with Essential Graphics panel
Use this page to know about the Essential Graphics panel in After Effects and how to work with Motion Graphics templates.
You can setup this rig for Essential Graphics for easier control or to use in Premiere Pro.
Use expressions to create drop-down lists in Motion Graphics templates
Create Motion Graphics templates with drop-down lists that allow users to customize them in Premiere Pro.
Drop down menu feature in AE.
Anchor Point Expressions in After Effects
A quick tutorial on using the sourceRectAtTime function in After Effects to fix the anchor point of a layer to a specific point. Especially useful when working with text or shape layers. Lower thirds anyone?
Lock the anchor point of the rig so it doesn't move around as time changes.
gfxhacks’s gists
GitHub Gist: star and fork gfxhacks’s gists by creating an account on GitHub.