• Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
    Dedicated community for Japanese speakers
  • 한국 커뮤니티
    Dedicated community for Korean speakers
Exit
1

Trouble with Easing Dynamic Layer Sorting Transitions in After Effects Expression

Community Beginner ,
Jan 24, 2025 Jan 24, 2025

Copy link to clipboard

Copied

Hi everyone,

I'm having trouble with an expression, and after trying multiple workarounds, I still can't get it to work. Maybe you have an idea or a hint?

Here's my setup:

  • I have several text layers (Item01, Item02, etc.), each with a slider called "Value."
  • The slider values change over time on different layers.
  • I want to sort the layers dynamically, so the layer with the highest value is always on top. If the values change, the order updates automatically. This part is already working!

 

The issue is that the position changes instantly, and I want to smooth the transition using ease or linear. But no matter what I try, I can't get it to work properly.

 

I've experimented with different approaches. This (below) method works for the first few seconds, but later in the timeline, the smooth transition stops working. I cant figure out to get the ease working with the right timing, the problem is (i guess) that the calculation for the ease time is wrong...but i dont know how to fix it...i also tried it like checking when the value changes and then easing and so on....nothing really worked

Any advice or solutions would be greatly appreciated!

 


Can someone give me a helping hand? 😄



Code:

 

// Create an array to store the text values and their corresponding layer indices
var textValues = [];

// Loop through all layers to get their text values
for (var i = 1; i <= thisComp.numLayers; i++) {
    var layer = thisComp.layer(i);
    // Check if the layer name matches the pattern "ItemXX"
    if (layer.name.match(/^Item\d+$/)) {
        var textValue = parseFloat(layer.effect("Value")(1).value);
        if (!isNaN(textValue)) { // Ensure the text value is a number
            textValues.push({value: textValue, index: i});
        }
    }
}

// Sort the array based on text values in descending order
textValues.sort(function(a, b) {
    return b.value - a.value;
});

// Find the current layer's index in the sorted array
var currentIndex = textValues.findIndex(function(element) {
    return element.index == index;
});

// Calculate the new Y position based on the sorted order
var baseY = 300; // Starting Y position
var spacing = 150; // Space between each text layer
var newY = baseY + currentIndex * spacing;

// Interpolate and ease between the current position and the new position
var currentPosition = value;
var transitionDuration = 1; // Duration of the transition in seconds

// Calculate the eased position
var t = (time - inPoint) / transitionDuration;
t = Math.min(Math.max(t, 0), 1); // Clamp t between 0 and 1
var easedY = ease(t, currentPosition[1], newY);

// Return the new position with easing
[currentPosition[0], easedY];

 

 

TOPICS
Error or problem , Scripting

Views

146

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines

correct answers 2 Correct answers

Community Expert , Jan 24, 2025 Jan 24, 2025

Adding ease to the functionality of your expression ups the complexity considerably because the expression now needs to know how long it's been since its rank has changed. In many cases, this info is not easily obtained. Your expression may have to go back in time, frame-by-frame to discover when its rank changed and subtract that time from the current time to calculate how far along it is in the ease operaion. You should only have to go back as far as transitionDuration because if it goes that

...

Votes

Translate

Translate
Community Beginner , Jan 29, 2025 Jan 29, 2025

Hey Dan thanks again for the hint and the helping hand! 
This is my solution now, and it works 🙂 ... juhuuu ...
Took me quit a while!
One problem I was just figuring out at last, i was always working with "transform.position.valueAtTime(time)" so figure out what the position of the layer is right now. But it somehow always was the wrong postition value , so i had to calculated it with the last rank. Kind of simple but took me a while :D...



// Initialize variables
var textValues = [];
var rankChange
...

Votes

Translate

Translate
Community Expert ,
Jan 24, 2025 Jan 24, 2025

Copy link to clipboard

Copied

Adding ease to the functionality of your expression ups the complexity considerably because the expression now needs to know how long it's been since its rank has changed. In many cases, this info is not easily obtained. Your expression may have to go back in time, frame-by-frame to discover when its rank changed and subtract that time from the current time to calculate how far along it is in the ease operaion. You should only have to go back as far as transitionDuration because if it goes that far back and its rank hasn't changed, it can safely assume that it should be settled in at its current rank. There may be simplifications available, depending on how and when the sliders can change, but what I've described should work in the worst-case scenario. There could be additional complications though, if the layer's rank can change more than once during a single transitionDuration period.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Jan 27, 2025 Jan 27, 2025

Copy link to clipboard

Copied

 

Good Morning Dan,
Thanks so much for your reply and effort!

You’re absolutely right, and I’ll give your approach a try. I actually tried something similar before by checking the position one second back.

I’ll share my experiences once I’ve tested it out.

Do you happen to have a completely new approach as well? I also tried doing it without easing, just creating it myself, but the issue always comes down to calculating the time.

There’s no simple way to do something like this, right? Since the problem is with calculating the time:

 

 

if (oldPosition != newPosition) ease for one second until oldPosition == newPosition.

 

I’ll give it another shot and see how it goes.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Beginner ,
Jan 29, 2025 Jan 29, 2025

Copy link to clipboard

Copied

LATEST

Hey Dan thanks again for the hint and the helping hand! 
This is my solution now, and it works 🙂 ... juhuuu ...
Took me quit a while!
One problem I was just figuring out at last, i was always working with "transform.position.valueAtTime(time)" so figure out what the position of the layer is right now. But it somehow always was the wrong postition value , so i had to calculated it with the last rank. Kind of simple but took me a while :D...



// Initialize variables
var textValues = [];
var rankChangeTime = 0;
var oldRank = 0; // Store the previous rank
var moverPos = thisComp.layer("MOVER").transform.position.value;

// Get transition duration from the "Controller" slider
var transitionDuration = thisComp.layer("Controller").effect("Transition Duration")(1);

// Gather all text values and their layer indices
for (var i = 1; i <= thisComp.numLayers; i++) {
    var layer = thisComp.layer(i);
    if (layer.name.match(/^Item\d+$/)) { // Check for matching layer names
        var textValue = parseFloat(layer.effect("Value")(1).value);
        if (!isNaN(textValue)) {
            textValues.push({value: textValue, index: i});
        }
    }
}

// Sort the text values in descending order
textValues.sort(function(a, b) {
    return b.value - a.value;
});

// Find the current layer's index in the sorted array
var currentIndex = textValues.findIndex(function(element) {
    return element.index == index;
});

// Check for rank changes by iterating backward in time
var lastRank = currentIndex;
for (var t = time; t >= thisComp.displayStartTime; t -= thisComp.frameDuration) {
    var sortedAtTime = textValues.slice(); // Clone the array
    for (var j = 0; j < sortedAtTime.length; j++) {
        var layerAtTime = thisComp.layer(sortedAtTime[j].index);
        sortedAtTime[j].value = parseFloat(layerAtTime.effect("Value")(1).valueAtTime(t));
    }
    sortedAtTime.sort(function(a, b) {
        return b.value - a.value;
    });
    var rankAtTime = sortedAtTime.findIndex(function(element) {
        return element.index == index;
    });

    if (rankAtTime != lastRank) {
        rankChangeTime = t; // Store the time of rank change
        oldRank = rankAtTime; // Store the old rank based on sorting, not position
        break;
    }
}

// Calculate the time difference from the current time
var timeSinceChange = time - rankChangeTime;

// Calculate the Y positions based on rank, not transform position
var baseY = thisComp.layer("Controller").effect("Base Y")(1);
var spacing = thisComp.layer("Controller").effect("Spacing")(1);
var newY = baseY + currentIndex * spacing;
var oldY = baseY + oldRank * spacing; // Calculate oldY based on old rank

// Interpolate the position
var easedY = ease(Math.min(timeSinceChange / transitionDuration, 1), oldY, newY);

[moverPos[0], easedY+moverPos[1]];

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines