DEV Community

Cover image for Project: Spreadsheet To Video, Kinetic Text
Kat
Kat

Posted on • Edited on

Project: Spreadsheet To Video, Kinetic Text

Contents

Introduction

Recently I set myself the target of writing expressions for After Effects which allow me to animate text, song lyrics, or captions, using a spreadsheet to dictate the copy and timings. Here is how I achieved this.

Initial Testing With Arrays

Before creating my .csv file, I wanted to work with some arrays to ensure I was able to get my expression working the way I wanted. In the "Source Text" parameter of a text layer, I create my arrays:

var textCopy = ["Apple", "Banana", "Strawberry", "Orange", "Peach"];
var textDuration = [.5, 1, .5, 1, 1];
Enter fullscreen mode Exit fullscreen mode

textCopy is the text I want to see on screen. Meanwhile, textDuration is the length of time a line is on screen, in seconds. My idea was to look at the textDuration entries and cross reference them with time. Once time = first number in the textDuration array, After Effects would add the second entry to work out the total duration. Then, once time = first entry + second entry, the next number in the array would be added, and so on, until all the numbers in the array were added together. For this, I used for loops. The first for loop was created to calculate the totalDuration:

var sum = 0;
var totalDuration = [];

for (i = 0; i < textDuration.length; i++) {
    sum = sum + textDuration[i];
    totalDuration.push(sum);
}
Enter fullscreen mode Exit fullscreen mode

In this loop, the textDuration array entries are added together one by one, using the sum variable to store them in temporarily, and then pushed to the totalDuration array before a new number is added. The loop continues until it cycles through all the numbers in the textDuration array.

I then create a second for loop, using the new totalDurations array:

var entry = 0;
for (i = 0; time >= totalDuration[i] && time < totalDuration[totalDuration.length-1]; i++){
    if (true) entry++;
}
Enter fullscreen mode Exit fullscreen mode

For this loop, I'm telling After Effects to loop only when time is equal to or bigger than the current totalDuration entry, but lower than the very last entry of the totalDuration array. If both of these are true, it will add 1 to the entry variable.

Lastly, I create an if statement telling After Effects what to display in the text layer:

if (time < totalDuration[totalDuration.length-1])textCopy[entry]
    else "Thanks For Watching!"
Enter fullscreen mode Exit fullscreen mode

If time is less than the last entry in totalDuration, it will cycle through the copy, using the for loops to update the array index. However once it reaches the end, it will display "Thanks For Watching!", signifying the end of the loop. This is to stop any errors from appearing, if the timeline is longer than totalDuration's last value. If you simply prefer the text to disappear once it reaches the end, you can delete the text from between the quotes on the else statement.

array powered animation gif

Now that I had a proof of concept, I was ready to introduce my .csv file.

Setting Up My Spreadsheet

It was time to set up my spreadsheet. I decided I would need 3 columns: "Text", "Text Duration" and "Total Duration". The Total Duration column would mean I could lose the for loop which added up the Text Durations and clean up my script.

I use the Total Duration column to work out the value of my Text Duration, so I can simply scroll through my audio and note down the end of each of the lines. If at any point you want to include a pause, simply leave the Text column empty for that row, and update the Duration columns accordingly. At this point, the Line Duration column seems redundant now that I have a Total Duration column, but I've kept it in the spreadsheet for animation purposes later.

speadsheet screen shot

Save the spreadsheet as a .csv file, so it can be imported into After Effects.

Using Data From The .CSV File

Actually using data from the spreadsheet was a little more complicated than I had initially thought. My main hurdle was trying to figure out how to cycle through the rows. I wanted to make this script adaptable so it would work no matter the number of rows in the spreadsheet. Therefore, I needed a way to count the number of rows, to work out the total.

First things first, I needed to reference the .csv file with the expression. I replace my arrays with this:

var data = footage("SPREADSHEET.csv");
Enter fullscreen mode Exit fullscreen mode

SPREADSHEET.csv should be the name of your spreadsheet in the project panel.

Now I was successfully telling After Effects to look at the information in the .csv file, it was time to start pulling information.

But in order to do that, I needed to count the rows in the .csv file. I came up with the idea of turning the data from the .csv file into a string, splitting the string on every line break, then calculating the length to get the number of entries. However, I wasn't quite sure how to turn my .csv file data into a string from within an After Effects parameter. Luckily, thanks to this post on Creative Cow, I was able to find I could do what I needed using the sourceText function. Like so:

var dataSplit = data.sourceText.split(/\n/);
var rows = dataSplit.length-2;
Enter fullscreen mode Exit fullscreen mode

The sourceText function transforms the data in the .csv file into a string. Then, the split function is separating that string line by line, searching for line breaks (represented by "\n"). We can measure how many times our string was split with the length function. To calculate the number of rows correctly, we need to subtract 2 from this number: 1 to account for the headings on our spreadsheet, and 1 because our index starts at 0.

Now that I had calculated the number of rows in the .csv file, I could go ahead and make my for loop:

var entry = 0;
for (i = 0; i <= rows && time >= data.dataValue([2, i]); i++){
    if (true) entry++
};
Enter fullscreen mode Exit fullscreen mode

Similar to the initial tests, while the variable entry is less than the variable rows, and time is more than the current total duration, entry's value increases by 1. This means when time is less then the current total duration, After Effects will look to the next one, until it has cycled through all entries in the column.

Finally, I needed to rewrite the last if statement to complete the expression:

if (time < data.dataValue([2, rows])) data.dataValue([0, entry])
    else "Thanks For Watching!"
Enter fullscreen mode Exit fullscreen mode

Again, like the tests, while time is less than the very last value in the total duration column, the for loop will provide the timings to tick through all instances of the text column in the spreadsheet. Then, once it has gone through all entries, it will finish by flashing up the message, "Thanks For Watching!"

speadsheet powered animation gif

And there it is, the basic expression! Pasted into the sourceText parameter of a text layer, it will allow the text to cut from one line to another, using timings specified in the .csv file:

var data = footage("SPREADSHEET.csv");


//Number of rows
var dataSplit = data.sourceText.split(/\n/);
var rows = dataSplit.length-2;


//For loop 
var entry = 0;
for (i = 0; i <= rows && time >= data.dataValue([2, i]); i++){
    if (true) entry++
};


//Return
if (time < data.dataValue([2, rows])) data.dataValue([0, entry])
    else "Thanks For Watching!"
Enter fullscreen mode Exit fullscreen mode

This is the basic concept which I set out to achieve. However, I wanted to see how far I could push things. So I thought about how I could format the text, and how I could create more interesting motion.

Adding Line Breaks

The first thing I wanted to add was the ability to include line breaks in the copy. Since the expression relies on the line breaks in the .csv file to separate the entries, it would break the expression to include line breaks in the copy of the spreadsheet.

The first work around I could think of was simply applying this code to a layer with a text box, rather than a simple text layer:

thanks for watching text box line break

This works if you don't mind where your line breaks fall. However, I wanted an option which gave the user control of where to put their line breaks. So I settled on the idea that a symbol could be used in the spreadsheet, and replaced later by the expression.

I decided to use an underscore. I added one to my spreadsheet as an example:

speadsheet screen shot underscore

and updated my .csv file in After Effects. To finish, I added the replace function to the end of the dataValue in the final if statement:

if (time < data.dataValue([2, rows])) data.dataValue([0, entry]).replace(/_/g, "\n")
    else "Thanks For Watching!"
Enter fullscreen mode Exit fullscreen mode

For the first argument, I placed an underscore between the slashes to specify the symbol I wanted to replace, and added the g modifier (the global modifier) to replace all instances. The second argument is what the function is replacing it with (again, "/n" meaning a line break). So now the expression searches for "_" and replaces it with a line break.

mango lychee line break

Because the intention of this expression is to stop the user from editing the code themselves, I felt that having the line breaks be specified in the spreadsheet was the best course of action.

If you want to include spaces on either side of your symbol for better readability in your spreadsheet, you can:

replace(/ _ /g, "\n")
Enter fullscreen mode Exit fullscreen mode

And if you'd rather use a different symbol, simply change the underscore to whatever symbol works best for you.

Aligning The Text

Now that I have line breaks, I have a new formatting issue: aligning my text. While After Effects provides horizontal alignment tools for text, it does not provide vertical alignment tools. Therefore, if our text drops onto extra lines, it will always drop "down" in space, and make our text look off center.

To correct this, I will need to fix the anchor point of the text layer, so no matter the size of the layer, it calculates where it should be to keep the text formatted correctly. I will start with center alignment, or keeping the anchor point in the center of the text layer.

This is fairly straightforward, and requires a simple expression written in the anchor point parameter of the layer:

var copy = thisLayer;

var cWidth = copy.sourceRectAtTime().width;
var cHeight = copy.sourceRectAtTime().height;
var cTop = copy.sourceRectAtTime().top;
var cLeft = copy.sourceRectAtTime().left;

var x = cLeft + cWidth / 2;
var y = cTop + cHeight /2;

[x, y]
Enter fullscreen mode Exit fullscreen mode

With the function sourceRectAtTime, we are able to measure the size of the text layer. As the text changes, its width, height, top, and left values will change. We can find the center of the layer by creating an x and y variable. For the x, I add the left and width values to get the right side of the layer, then divide by 2. For the y, I add the top and the height values to get the bottom of the layer, then divide by 2. When these variables are entered as coordinates for our anchor point, they will always sit in the center of the layer.

scrub through titles centered gif

This is useful for full screen graphics, and caption style text.

scrub through caption style gif

If you need the anchor point to be in a different place, you will need to adjust the equation. Such as:

Top-Center:

x = cLeft + cWidth / 2;
y = cTop;
Enter fullscreen mode Exit fullscreen mode

Bottom-Center:

x = cLeft + cWidth / 2;
y = cTop + cHeight;
Enter fullscreen mode Exit fullscreen mode

Top-Left:

x = cLeft;
y = cTop;
Enter fullscreen mode Exit fullscreen mode

Just remember: Top and left aligned only need the top and left values, right and bottom aligned need the full left + width and top + height values, and center alignment need the full values to be divided by 2.

Text Size

Other formatting options I was considering was text size. What if I want different lines to be a different size text? Again, a simple way of adjusting this is with keyframes at every line change. However this is a manual, time consuming process. Also, I want to assume the user has very basic After Effects skills. So I wanted to connect the font size to the spreadsheet.

I added another column for text size.

spreadsheet screen shot, text size

Once I saved a new .csv file, I knew I could cycle through these values in the same way I was cycling through the Text column. But what I needed to figure out was how to format the text. At first, I tried the usual method of formatting text in After Effects:

text.sourceText.style.setFontSize(data.dataValue([3, entry]))
Enter fullscreen mode Exit fullscreen mode

Since our new Font Size column is number 3 in our .csv file (because the first entry starts at 0), the number will change as the copy does. However, because style references sourceText, this does not work with the rest of our expression. If it is placed before our final if statement, it is ignored. If it is placed after, it supersedes the if statement and reverts the text layer to its original source text.

There was a simple fix however. All I had to do was .setText using the data from the spreadsheet. This meant I could add the styling options to the final if statement:

if (time < data.dataValue([2, rows])) text.sourceText.style.setFontSize(data.dataValue([3, entry])).setText(data.dataValue([0, entry]).replace(/_/g, "\n"))
    else "Thanks For\nWatching!"
Enter fullscreen mode Exit fullscreen mode

And it works! However, this changes the font size only. To update the leading, I will have to add that to the style options too:

if (time < data.dataValue([2, rows])) text.sourceText.style.setFontSize(data.dataValue([3, entry])).setLeading(data.dataValue([3, entry])).setText(data.dataValue([0, entry]).replace(/_/g, "\n"))
    else "Thanks For\nWatching!"
Enter fullscreen mode Exit fullscreen mode

I've opted to set the leading to the same value as my font size. Now the font size will change along with the text.

Image description

To alter the leading as needed, without editing the expression, I created a slider, to adjust a percentage of the Text Size column. A percentage rather than adding a number will keep the line spacing between different copy consistent.

However, as you can tell, my final if statement is becoming quite long. I needed to start storing information in variables to make this more manageable. The trouble was, if I took the dataValue's out of the if statement, the entry variable would cause an error, its last value ticking up 1 higher than the number of entries in the spreadsheet. This perplexed me for a long time, but the answer to solve this issue was obvious. I could simply clamp the entry value to ensure it never went over the max number of rows. Once I figured this out, I was able to make variables to store data:

var maxDuration = data.dataValue([2, clamp(entry, 0, rows)]);
var copy = data.dataValue([0, clamp(entry, 0, rows)]);
var copySize = data.dataValue([3, clamp(entry, 0, rows)]);
var copyStyle = text.sourceText.style.setFontSize(copySize).setLeading(copySize);
Enter fullscreen mode Exit fullscreen mode

Taking my if statement down to a much more manageable:

if (time < maxDuration) copyStyle.setText(copy.replace(/_/g, "\n"))
    else "Thanks For\nWatching!"
Enter fullscreen mode Exit fullscreen mode

Hooray! Now I'd tidied up my code, I turned my attention back to my slider. I created a new variable for my copyLeading.

var copyLeading = (copySize/100) * effect("Leading Padding")("Slider");
var copyStyle = text.sourceText.style.setFontSize(copySize).setLeading(copySize + copyLeading);
Enter fullscreen mode Exit fullscreen mode

mango lychee leading gif

The final thing to consider about text size - the sign off message is not affected by the styling, as it is not from the spreadsheet. So, if you want to have a sign off message, you need to account for that in the else statement.

var signOffStyle = text.sourceText.style.setFontSize(150).setLeading(150);

if (time < maxDuration) copyStyle.setText(copy.replace(/_/g, "\n"))
    else signOffStyle.setText("Thanks For\nWatching!")
Enter fullscreen mode Exit fullscreen mode

Here I have just input the values, but this should also be connected to sliders to make the options editable for the user.

Using The Essential Graphics Panel To Control Formatting Options

Now that sliders have been introduced to the project, I need to make sure the user is able to change their value without being confused. For this, I like to set up my Essential Graphics panel. This panel is fairly new, and is used to create .mogrt files (motion graphics templates) for use in Premiere. However, I think it also does a wonderful job of facilitating After Effects templates, by being a place where all editable values can be placed for the user to find (no more having to search every precomp until you find the layer you need!).

First things first, I want to make sure all the parameters a user could want for changing the sign off message are in place. I create a new text layer, and connect its source text to my expression to create a place for the user to type their own message. Next, I make some more sliders and create more variables to control the sign off font size, and leading. Here is the list of my sign off variables:

var signOffMessage = thisComp.layer("SIGN OFF").text.sourceText;
var signOffSize = effect("Sign Off Font Size")("Slider");
var signOffLeading = (signOffSize/100) * effect("Sign Off Leading Padding")("Slider");
var signOffStyle = text.sourceText.style.setFontSize(signOffSize).setLeading(signOffSize + signOffLeading);
Enter fullscreen mode Exit fullscreen mode

and my updated else statement:

else signOffStyle.setText(signOffMessage)
Enter fullscreen mode Exit fullscreen mode

At this point, I open my Essential Graphics panel (Window > Essential Graphics). I drag in the parameters I want my user to use, such as the sign off message's source text, and all of my sliders. I make some folders to keep my parameters organised. Lastly, I turn off, lock, and shy-guy away my sign off message text layer, so the user isn't distracted.

Essential graphics panel

Now all parameters which need to be editable for the user connected to my code are present in the panel. What a tidy template!

Essential graphics effecting the sign off message gif

If you want to add more parameters to your Essential Graphics panel, simply drag and drop them in as desired.

Full expression with new formatting:

Source Text

var data = footage("SPREADSHEET.csv");

//Number of rows
var dataSplit = data.sourceText.split(/\n/);
var dataFirstRow = dataSplit[0].split(",");
//dataSplit.length-2 // number of Row
//dataFirstRow.length // number of Columns

var rows = dataSplit.length-2;

//For loop 
var entry = 0;
for (i = 0; i <= rows && time >= data.dataValue([2, i]); i++){
    if (true) entry++
};


//Return
var maxDuration = data.dataValue([2, clamp(entry, 0, rows)]);
var copy = data.dataValue([0, clamp(entry, 0, rows)]);
var copySize = data.dataValue([3, clamp(entry, 0, rows)]);
var copyLeading = (copySize/100) * effect("Leading Padding")("Slider");
var copyStyle = text.sourceText.style.setFontSize(copySize).setLeading(copySize + copyLeading);
var signOffMessage = thisComp.layer("SIGN OFF").text.sourceText;
var signOffSize = effect("Sign Off Font Size")("Slider");
var signOffLeading = (signOffSize/100) * effect("Sign Off Leading Padding")("Slider");
var signOffStyle = text.sourceText.style.setFontSize(signOffSize).setLeading(signOffSize  + signOffLeading);

if (time < maxDuration) copyStyle.setText(copy.replace(/_/g, "\n"))
    else signOffStyle.setText(signOffMessage)
Enter fullscreen mode Exit fullscreen mode

Anchor Point

var copy = thisLayer;

var cWidth = copy.sourceRectAtTime().width;
var cHeight = copy.sourceRectAtTime().height;
var cTop = copy.sourceRectAtTime().top;
var cLeft = copy.sourceRectAtTime().left;

var x = cLeft + cWidth / 2;
var y = cTop + cHeight /2;

[x, y]
Enter fullscreen mode Exit fullscreen mode

Animation: Opacity

Now the basic script is written, and formatting options have been applied, the fun part can begin! Animation.

I decided to start simple. My goal was to create a simple fade in and out for every line, to soften the hard cut. The opacity parameter is also only 1 value, so it will be easier to work with as we start out.

Since we will still need to know when our lines change, we need to keep the start of our code the same:

var data = footage("SPREADSHEET.csv");

//Number of rows
var dataSplit = data.sourceText.split(/\n/);
var dataFirstRow = dataSplit[0].split(",");
//dataSplit.length-2 // number of Row
//dataFirstRow.length // number of Columns

var rows = dataSplit.length-2;

//For loop 
var entry = 0;
for (i = 0; i <= rows && time >= data.dataValue([2, i]); i++){
    if (true) entry++
};
Enter fullscreen mode Exit fullscreen mode

But now, what I want to return is a change in value. Essentially, at the start of every line, the opacity will change from 0 to 100, over a duration set by the user. Then, at the end of the line, the opacity changes from 100 to 0 at the same speed, so there is a smooth transition between each line. This means as one animation brings the line on, and the reverse animation brings it off.

With that reasoning, I set up the variables I would need to make this possible.

var maxDuration = data.dataValue([2, rows]);
var lineDuration = data.dataValue([1, clamp(entry, 0, rows)]);
var totalDuration = data.dataValue([2, clamp(entry, 0, rows)]);

var startAni = totalDuration - lineDuration;
var endAni = totalDuration;
var aniDuration = (1*thisComp.frameDuration) * effect("aniDuration")("Slider");
Enter fullscreen mode Exit fullscreen mode

At last, a reason for the line duration column on our spreadsheet! Having this data accessible in our .csv file means less calculations for After Effects, meaning more efficient code.

lineDuration and totalDuration match their appropriate columns in the spreadsheet. Meanwhile, maxDuration is the last entry of totalDuration, and will be the time it takes to cycle through all of our text.

With these variables set, I can now set the animation on, and animation off times. Since I need the start of the animation to be at the beginning of the line, I set startAni to totalDuration - lineDuration to find that value. Meanwhile, endAni = totalDuration, the time the current line animates off of screen.

Lastly, I create a variable so I can control the speed of the animation. I create a new slider, and multiply it by 1*thisComp.frameDuration. This way, no matter what the composition's frame rate is set to, the slider will always be setting the animation's duration in frames. My composition is at 25fps, so I set my aniDuration to 10 frames.

With all these variables set, it's time to use a trusty ease function to create the move from 0 opacity to 100.

var aniOn = ease(time, startAni, startAni + aniDuration, 0, 100);
var aniOff = ease(time, endAni - aniDuration, endAni, 100, 0);
Enter fullscreen mode Exit fullscreen mode

the aniOn variable will change the opacity parameter from 0 to 100, between the start time of each line, and the start time + the animation duration set by the slider. Meanwhile, the aniOff variable will change the opacity parameter from 100 to 0, between the end time of each line - the animation duration, and the end time.

In order to use both of these animations, we need to set an if statement. I decided that the time the line is on screen should be divided by 2, giving equal time for the line to animate on and off. To work out where the midpoint of each line sits, I just have to add the startAni and endAni together, and divide by 2.

if (time < (startAni + endAni)/2) aniOn
    else aniOff
Enter fullscreen mode Exit fullscreen mode

And with that, we have our first bit of animation!

animating opacity gif

But I can't celebrate just yet. This current if statement doesn't include the sign off message. At present, the text will animate off after the last line in our spreadsheet, making the sign off message invisible.

To fix this, I need to make a variable for the sign off message to animate:

var signOffAni = ease(time, maxDuration, maxDuration + aniDuration, 0, 100);
Enter fullscreen mode Exit fullscreen mode

and nest my current if statement inside another if statement, to let After Effects know what should happen after all the entries of the spreadsheet have been read.

if (time < maxDuration)
    if (time < (startAni + endAni)/2) aniOn
        else aniOff
    else signOffAni
Enter fullscreen mode Exit fullscreen mode

Now, when the current time is less than the total time it will take to cycle through all entries on the spreadsheet, After Effects then checks whether or not the current time is in the first or second half of the time the current line is on screen. Once it has that information, it chooses whether or not to display the startAni or endAni variable. But - if the current time is equal to or more than the maxDuration, it will display the new signOffAni variable, accounting for the sign off message.

But before I could move on to testing other bits of information, I started to wonder what would happen if the aniDuration was set to 0. Sadly, this breaks things a little bit. The first half of the time it takes a line to display is permanently set to 0 opacity, while the second half is permanently set to 100. True, I could avoid setting the slider to 0, but then again, wouldn't it be cool if setting the slider to 0 turned the animation off, so we could choose whether or not we wanted to use it?

And I can do that! It involves further nesting my if statement:

if (time < maxDuration)
    if (time < (startAni + endAni)/2)
        if (aniDuration == 0) value
            else aniOn
        else
            if (aniDuration == 0) value
                else aniOff
    else
        if (aniDuration == 0) value
            else signOffAni
Enter fullscreen mode Exit fullscreen mode

Breaking this down, everytime the previous if statement gave a value, I created a new condition for After Effects to check. If the duration slider was set to 0, the if statement would set the opacity to it's default value, turning the animation off. However, if it was any other value, it would run as expected.

By setting the opacity to its default value rather than 100, it allows the user to change the opacity of the text layer. After doing this, the thought occurred to me that the ease functions should also be changed to the default layer value, to give the user more customisation options.

var aniOn = ease(time, startAni, startAni + aniDuration, 0, value);
var aniOff = ease(time, endAni - aniDuration, endAni, value, 0);
var signOffAni = ease(time, maxDuration, maxDuration + aniDuration, 0, value);
Enter fullscreen mode Exit fullscreen mode

With that, I have some seriously cool customisation options for the first animatable parameter of the script!

Full Opacity Expression

var data = footage("SPREADSHEET.csv");

//Number of rows
var dataSplit = data.sourceText.split(/\n/);
var dataFirstRow = dataSplit[0].split(",");
//dataSplit.length-2 // number of Row
//dataFirstRow.length // number of Columns

var rows = dataSplit.length-2;

//For loop 
var entry = 0;
for (i = 0; i <= rows && time >= data.dataValue([2, i]); i++){
    if (true) entry++
};


//Return
var maxDuration = data.dataValue([2, rows]);
var lineDuration = data.dataValue([1, clamp(entry, 0, rows)]);
var totalDuration = data.dataValue([2, clamp(entry, 0, rows)]);

var startAni = totalDuration - lineDuration;
var endAni = totalDuration;
var aniDuration = (1*thisComp.frameDuration) * effect("Opacity Animation Duration")("Slider");

var aniOn = ease(time, startAni, startAni + aniDuration, 0, value);
var aniOff = ease(time, endAni - aniDuration, endAni, value, 0);
var signOffAni = ease(time, maxDuration, maxDuration + aniDuration, 0, value);


if (time < maxDuration)
    if (time < (startAni + endAni)/2)
        if (aniDuration == 0) value
            else aniOn
        else
            if (aniDuration == 0) value
                else aniOff
    else
        if (aniDuration == 0) value
            else signOffAni
Enter fullscreen mode Exit fullscreen mode

Animation: Position

While I certainly don't intend to walk through every single parameter in the same way, I do want to write up how I am choosing to animate the parameters with more than 1 value.

However, the position parameter is unique, in that it can be separated.

Separated position screen shot

Because of this, controlling each value was simple. I copy-pasted my code from the opacity parameter, and changed the applicable variables.

Here are the variables under the X Position:

var maxDuration = data.dataValue([2, rows]);
var lineDuration = data.dataValue([1, clamp(entry, 0, rows)]);
var totalDuration = data.dataValue([2, clamp(entry, 0, rows)]);

var startAni = totalDuration - lineDuration;
var endAni = totalDuration;

var aniDuration = (1*thisComp.frameDuration) * effect("X Position Animation Duration")("Slider");
var posMoveIn = effect("X position move in")("Slider");
var posMoveOut = effect("X position move out")("Slider");

var aniOn = ease(time, startAni, startAni + aniDuration, value + posMoveIn, value);
var aniOff = ease(time, endAni - aniDuration, endAni, value, value + posMoveOut);
var signOffAni = ease(time, maxDuration, maxDuration + aniDuration, value + posMoveIn, value);
Enter fullscreen mode Exit fullscreen mode

I created a few more sliders for the position parameter than I did for opacity, to control the movement in and out separately. I made sure to add posMoveIn to the in-animation variables, and posMoveOut to the out-animation.

I then did all of this for the Y Position. If you also want to animate within Z space, make sure to enable 3D on the layer, and do the same for its Z Position.

With that in place, I could now animate the position of my layers in and out for each line.

position x gif

position y gif

position xy gif

And, if I wanted, I could combine the position and the opacity animation.

position opacity gif

Animation: Scale

Unlike position, we are unable to separate our scale values. This is because we are able to tether and untether our x and y scale values with a switch in our properties panel - which under normal circumstances works perfectly fine.

scale chain link

However, this means we will have to do some more thinking about our expression, if we want to have the option of affecting the x and y scale independently.

Here's how I worked this out.

I started by making a dropdown menu. I made 2 options, combined scale and separated scale.

scale dropdown menu options

I then added 9 more sliders. 3 for affecting the animation duration, animation in, and animation out while the scale was unified. 3 for those aspects for the x scale only. And 3 for those aspects for the y scale only.

I then created all my variables. There were considerably more than for position, as effectively, I had to times the number I needed by 3:

var maxDuration = data.dataValue([2, rows]);
var lineDuration = data.dataValue([1, clamp(entry, 0, rows)]);
var totalDuration = data.dataValue([2, clamp(entry, 0, rows)]);

var startAni = totalDuration - lineDuration;
var endAni = totalDuration;
var aniDuration = (1*thisComp.frameDuration) * effect("Scale Animation Duration")("Slider");

var scaleDrop = effect("Scale Dropdown")("Menu");

var scaleUniIn = effect("Scale Unified scale In")("Slider");
var scaleUniOut = effect("Scale Unified scale Out")("Slider");
var scaleXIn = effect("Scale X scale In")("Slider");
var scaleXOut = effect("Scale X scale Out")("Slider");
var scaleYIn = effect("Scale Y scale In")("Slider");
var scaleYOut = effect("Scale Y scale Out")("Slider");

var aniOnUni = ease(time, startAni, startAni + aniDuration, value[0] + scaleUniIn, value[0]);
var aniOffUni = ease(time, endAni - aniDuration, endAni, value[0], value[0] + scaleUniOut);
var signOffAniUni = ease(time, maxDuration, maxDuration + aniDuration, value[0] + scaleUniIn, value[0]);

var aniOnX = ease(time, startAni, startAni + aniDuration, value[0] + scaleXIn, value[0]);
var aniOffX = ease(time, endAni - aniDuration, endAni, value[0], value[0] + scaleXOut);
var signOffAniX = ease(time, maxDuration, maxDuration + aniDuration, value[0] + scaleXIn, value[0]);

var aniOnY = ease(time, startAni, startAni + aniDuration, value[1] + scaleYIn, value[1]);
var aniOffY = ease(time, endAni - aniDuration, endAni, value[1], value[1] + scaleYOut);
var signOffAniY = ease(time, maxDuration, maxDuration + aniDuration, value[1] + scaleYIn, value[1]);
Enter fullscreen mode Exit fullscreen mode

This also includes a variable for the dropdown menu.

From there, I had to extend my if statement again, to check what the dropdown menu was set to. If it was set to option 1, combined scale, it would display the unified variables. If it was anything else, it would display the separated variables.


if (time < maxDuration)
    if (time < (startAni + endAni)/2)
        if (aniDuration == 0) [value[0], value[1]]
            else
                if (scaleDrop == 1) [aniOnUni, aniOnUni]
                    else [aniOnX, aniOnY]
        else
            if (aniDuration == 0) [value[0], value[1]]
                else
                    if (scaleDrop == 1) [aniOffUni, aniOffUni]
                    else [aniOffX, aniOffY]
    else
        if (aniDuration == 0) [value[0], value[1]]
            else
                if (scaleDrop == 1) [signOffAniUni, signOffAniUni]
                    else [signOffAniX, signOffAniY]
Enter fullscreen mode Exit fullscreen mode

Once all these pieces were in place, the template could be toggled between a combined scale animation, and a separated scale animation.

scale animation options gif

Animation: Other Ideas

Using the techniques above, you could quite easily make a rotation option for your template.

However, why stop there? You could switch your layer to 3D mode (as mentioned before in the Animation: Position section) and introduce a camera. Moving a camera around across your text, while your text animates, can create some really interesting effects!

Alternatively, if you want to keep your layer 2D, you could introduce a null object, and parent your text layer to it. Then, try to move it around in weird and wonderful ways. Since all our expressions reference the original values of our text layer, parenting our layer shouldn't result in any nasty surprises. You could also add expressions to your null layer, so it animates as every line in the spreadsheet comes and goes.

You can introduce shape layers, footage, or colour changes. Perhaps even dive into the text animator and animate your text line by line, or letter by letter (although I found that you will need a separate layer to fix your anchor point for this). The sky is the limit!

Limitations

Well... that isn't strictly true. There are of course some limitations to creating a project this way.

Such as the number of lines the expression can support. The more rows in your .csv file, the more loops and calculations. Meaning the harder After Effects and your computer is going to have to work. This can make things run rather slow.

My suggestion, if you have lots of lines and it's causing too much lag in your project, is to separate your spreadsheet into multiple .csv files, and create videos in sections. You could then render each section separately, and stitch your video together in Premiere, or another editing program of choice.

Also, because this layer is only 1 text layer, there is no way for the transitions between your lines to overlap. If you require that, you will have to write an expression which works with 2 text layers. How I would test this is to have 2 spreadsheets, one with copy in the text column on all odd number rows, and the other with copy in the text column on all even number rows. That way, you could stagger the layers in such a way that the animation of the two overlap.

For similar reasons, adding a delay to the expression is also very tricky. I recommend accounting for the delay in your spreadsheet, rather than factoring it into After Effects, so the expression doesn't get more complex. You could make an extra column to adjust your total durations.

It is also important to note that this is a template project. No matter how much you add, it will never replace the almost limitless potential of a designer creating a project from scratch. If you need something truly unique and special, you're going to have to roll up your sleeves and prepare for creating the project by hand.

Conclusion And Learnings

I learned a lot from this project. This was the first time I ever used a for loop, which this project heavily relies on. It fact, it was a project full of first times: the first time importing data into After Effects using a .csv file; the first time working with text styling options, and the first time crafting complex, if statements. I learned how to turn data into a string in After Effects by using the sourceText function, and learned about the split, clamp and replace functions.

I am still experimenting with what I can do with this template. There may be a follow up article to this, with some of my more complex discoveries around using text animator and expression selector options, and creating variables for changing out the type of easing used to create the animations once I am satisfied.

Overall, this was a great project to boost my knowledge of javascript within After Effects, and I feel I have more than achieved what I set out to do.

If you have any questions, please leave a comment and I'll try to answer them. Alternatively, if you think there is a more efficient way of doing things than I have done them, please let me know.

Top comments (4)

Collapse
 
martinbaun profile image
Martin Baun

That is one of the best guides I've read. It's so clear and with simple examples, ideal for beginners

Collapse
 
kocreative profile image
Kat

Thank you! I always strive for clarity with my articles, so that's wonderful to hear. I'm glad you enjoyed it!

Collapse
 
striperks profile image
Striperks

Good post!

Collapse
 
kocreative profile image
Kat

Thank you!