Skip to main content
Participant
December 5, 2022
Question

Auto-Resizing Shape Layer/Rectangle (But With Multiple Lines Of Text)

  • December 5, 2022
  • 3 replies
  • 3474 views

Hello!!

 

I've seen many tutorials on how to make a shape layer (usually a rectangle) auto-resize to fit a line of text in After Effects. However, is there an expression to make a shape layer respond to two or more lines of text, auto-resizing to fit whichever is the longest line of text?

 

If anyone knows an answer to this or a tutorial that I may have missed, that would be appreciated!

This topic has been closed for replies.

3 replies

Community Expert
December 6, 2022

You can use sourceRectAtTime() to determine the size of each text layer, add the combined height and look for the widest layer. Using index instead of layer names this expression will give you the Rectangle Size for 3 text layers:

 

// Rectangle Path Size
// source layers 
src1 = thisComp.layer(index - 3);
src2 = thisComp.layer(index - 2);
src3 = thisComp.layer(index - 1);

// text size
tBox1 = src1.sourceRectAtTime();
tBox2 = src2.sourceRectAtTime();
tBox3 = src3.sourceRectAtTime();

// max width
tWidth = Math.max(tBox1.width, tBox2.width, tBox3.width).toFixed(2);

//  add leading 
lyrSpace = 30;

// total height
srcHeight = Math.ceil(tBox1.height + tBox2.height + tBox3.height + lyrSpace);
tHeight = srcHeight + lyrSpace;

// box size
txtBase = [tWidth, tHeight];

// padding control
roundPad = content("Rectangle 1").content("Rectangle Path 1").roundness;
hPad = 20;
vPad = 40;


box = [(hPad + roundPad)] + [txtBase[0], txtBase[1]];
x = box[0];
y = box[1] + vPad;

[x, y]

 

The hPad, vPad, and lyrSpace can be sliders.

 

The next problem you have to solve is the text layer position. Stack the second and third text layers using this expression:

 

srcLyr = thisComp.layer(index - 1);
srcScl = srcLyr.scale * .01;
srcPos = srcLyr.position;
srcRec = srcLyr.sourceRectAtTime();
srcH = srcRec.height;
srcT = srcRec.top;
lyrT = thisLayer.sourceRectAtTime().top;
srcOfst = (srcH + srcT) * srcScl[1];
linSp = 20;

[srcPos[0], srcPos[1] + srcOfst - lyrT + linSp]

 

The linSP can be a slider.

 

Add this expression to the shape layer position:

 

srcLyr = thisComp.layer(index - 3);
srcLyr.position

 

You also need to write expressions for Rectangle/Position, Rectangle/Roundness. This post is getting kind of long and confusing, so I just shared a project file that contains all of the expressions and something extra. You can save it as an animation preset. I have not compensated for different paragraph justifications on the text layers so they all need to match. 

 

I hope this helps.

 

Look for a tutorial series on sourceRectAtTime() that is going to demonstrate how to automate all kinds of text, shape, and ordinary layers.

Known Participant
July 18, 2023

Hi Rick, 

 

This project file is almost working for me, but I has to much "stuff" I don't need. I hope you can help me out, because I don't see what I can delete without messing up the whole thing or to make it work better.

 

I just want a column with 1 to 3 lines of text. The lines are left aligned and the column must be centered to the middle of the composition. So, for example, if line 3 is the longest line, the column of text must be aligned to the center. The baseline of the lines is fixed, so I don't need any custom leading.

Can you please help me out?

 

See enclosed my AEP based on your AEP. 

Many thanks!

Community Expert
July 18, 2023

I think that this is your design goal:

  1. Three equally spaced lines of text move up to their final position over a few frames
  2. Each line of text is revealed as it moves into position by an animated mask
  3. The stack of text layers needs uniform leading (space between the lines)
  4. The stack of text layers needs to be left justified and centered in the composition, no matter which line is longer

 

Let's take the design goals one at a time, starting with the first goal:

Step 1 - Moving text into position: Use keyframes or the time minus the layer in-point. I prefer expressions for this kind of move utilizing time minus the in-point. Because a mask or matte will reveal the text, the distance can be calculated by the height of the actual text using sourceRectAtTime().height. That part is pretty straightforward. You can add a bit of padding to height, then apply an expression like this to the anchor point to move the layers up just a little farther than they are high. By using the anchor point, you can still use a layer's height to stack them evenly.

 

h = sourceRectAtTime().height;
pad = 10;// extra distance for the move
yMove = h + pad;
t = (time - inPoint) / thisComp.frameDuration;
mov = 20; //number of frames for the move
newY = easeIn(t, 0, mov, - yMove, 0);
[value[0], value[1] + newY]

 

The move would start the text below the anchor point by the height of the text and move it up into its resting position without the expression. You could also multiply the layer's index by an offset value, keep all three text layers with the same in-point, and offset the timing by a specific value.

 

Step 2 - Animating a mask to reveal the text layer. Because the text moves up into position, you could move down the timeline until the text layer stops moving, carefully draw a mask that includes all of the text, then apply the Window/Create Nulls From Paths/Points Follow Nulls script. The created nulls would keep the mask from moving while the text moves into position. Your sample project has a little animation in the top right corner of the mask that you could control with a slightly modified copy of the Anchor Point expression text layer applied to the top right corner null. You could then shy the Null layers to keep the timeline clean. You could also tie the vertical position of the nulls to the text layer position so they would move with the layer. The simplest option would be to keyframe the mask as you did in your sample project.

 

Step 3 - Stacking the layer with uniform spacing is pretty straightforward. All you have to do is take the position of the first layer, add the distance between layers, then use the same expression again to stack the layers with universal spacing. A simple expression like this for the second and third layers would get them evenly spaced below and lined up with the top layer.

 

p = thisComp.layer(index - 1).position;
yOfst = 250; //space between layers
[p[0], p[1] + yOfst]

 

 Step 4 - Center the stack of layers horizontally and vertically in the composition. This is where sourceRectAtTime() comes into play again. You use it on the position property of the top layer. Adding the combined height of each layer plus the padding between layers allows you to center the layer stack vertically. That part is easy. Easier if you add a spacing slider to the top layer and use it for all three layers. Getting the X position is a little more complicated. You have to take find the layer with the maximum width. You can do that using :

 

Math.max(lyr1.width, lyr2.width, lyr3.width)

 

 

The expression applied to the top text layer that would center all three layers horizontally and vertically would look like this:

 

lyr1 = sourceRectAtTime();
lyr2 = thisComp.layer(index + 1).sourceRectAtTime();
lyr3 = thisComp.layer(index + 2).sourceRectAtTime();
w = Math.max(lyr1.width, lyr2.width, lyr3.width);
vSpace = 250 * 2;
h = (lyr1.top + lyr2.height + lyr3.height + vSpace)/2;
x = thisComp.width/2;
y = thisComp.height/2;
[x - w/2, y - h + lyr1.height]

 

If VSpae and pad were replaced with an Expression Control Slider on the top text layer, you could easily compensate for the height of any text layer. 

 

This will work with text of different point sizes as long as all text layers are left justified, the baseline shift is zero, and the point sizes are all the same. If you change the point of one of the layers, you will have to throw in sourceRectAtTime()top to maintain accurate vertical spacing and account for ascenders and defenders in the type.

 

I hope this gets you started.

(EDIT: I got kind of intrigued by this idea and made a sample comp for you to look at. It uses a slider to adjust the Spacing between layers, expressions to drive the masks on the second and third layers, and the distance between the keyframes on the top layer mask set the timing for the move.  Try downloading the file and see if you get some ideas.

 

If I get some time in the next few days, I will try setting this up so that Ascenders and Descenders in the text layers don't offset the centering by their height, and the text layers don't have to have the same font size.

 

Mathias Moehl
Community Expert
Community Expert
December 5, 2022

Pins & Boxes is your friend 🙂

 

Pins & Boxes can create "Pins" at the corners or edges of any layers, and these pins stay attached to the layer if it is changed (say if the text of a text layer is replaced, the layer is moved, scaled,...) In addition, Pins & Boxes can create boxes around arbitrary collections of pins. These boxes resize such that they always includeexactly  all the pins that belong to that box.
Hence, you can simply add pins to the corners of your two text layers and then create a box around all those pins.

You can also parent the second text layer to a pin of the first one, it you want the second text to stay attached to a corner of the first text, for example.

Mathias Möhl - Developer of tools like BeatEdit and Automation Blocks for Premiere Pro and After Effects
Dan Ebberts
Community Expert
Community Expert
December 5, 2022

It seems like sourceRectAtTime().width should give you the width of the longest line, so that part should be the same as for a single line.