In this article my goal is to explain the concept and uses cases as I would have liked to learn it.
The Basics: Function as a Property
In its most basic (and admittedly useless) form:
gsap.to('.my-element', {
y: function() {
// Simply sets the value as 10. No different than just putting y: 10
return 10;
}
});Cool, right? Not really.
But it gets more useful when you realize what GSAP passes to these functions.
Each function gets three arguments:
- index — the element's index in the animation target list
- target — the actual DOM element
- targets — the full array of all elements being animated
Going forward I'll refer to them as follows for the sake of clarity
- index
- currentElement
- allElements
Using functions like this lets you write context-aware animations that adapt to position, attributes, or even external data.
gsap.to('.lots-of-elements', {
height: function(index) {
// In this example, each element will be 10px taller than the last
return index * 10;
}
});A Few Practical Examples
Some simple ideas to get you started:
gsap.to('.lots-of-elements', {
height: function(index) {
// In this example, each element will be 10px taller than the last
return index * 10;
},
stagger: 0.1 // And a simple stagger to make it fancy
});gsap.to('.myFirstElement, .mySecondElement', {
scale: function(index, currentElement, allElements) {
// Check which element this is (useful for needle in a haystack scenarios)
if (currentElement === allElements[1]) {
return 10;
} else {
return 0;
}
}
});This kind of logic is super handy when you're targeting multiple elements but need one to behave differently — without hardcoding selectors or rewriting the animation for each case.
Modularity and Ease Math
You can also define your logic outside the tween, and GSAP will still handle the parameters correctly. Here's a modular approach using gsap.utils.pipe to build a value from multiple utility functions:
let elements = document.querySelectorAll('.lots-of-elements');
// Define a function that will take an index of an element
// and return a value weighted on an ease
let heightFn = gsap.utils.pipe(
gsap.utils.normalize(0, elements.length - 1),
gsap.parseEase("power2.out"),
gsap.utils.mapRange(0, 1, 0, elements.length * 10)
);
gsap.to(elements, {
height: heightFn
// Notice how we don't need to put the (index), because by default
// GSAP will pass all 3 values through if they can be.
// This means we can write our code in more modular ways.
});This reads a bit like a functional playground: normalize the index, ease it out, and then map it to a new range. You don't need to call the function yourself — GSAP will pass everything in behind the scenes. Which means you can build tiny, testable functions and still keep your tweens clean.
Attribute-Based Values
Another use case is pulling values directly from HTML attributes. This lets you keep logic in the markup — useful when clients (or teammates) want some control without editing JS files:
gsap.from('.element', {
y: function(index, currentElement) {
// Dynamic attribute-powered values
return currentElement.getAttribute('data-gsap-y') || 0;
}
});This one's especially handy in Webflow sites, where dynamic fields can inject values into attributes.
Arrow functions?
Notice how I'm always declaring the function anonymously.
y: function() {}Rather than an arrow function
y: ()=>{}This is because arrow functions don't have access to the 'this' keyword. So you wont be able to reference the current tween. I also just think it's cleaner to be a bit more verbose especially in the Webflow world where a lot of developers are inexperienced with writing any javascript at all
Final Thoughts
There's something really satisfying about using functions like this. It's one of those techniques that makes your animation logic cleaner, more flexible, and easier to reason about — especially when projects scale or switch hands.
If you do decide to go ahead with it, comment your code clearly. It's deceptively powerful and extremely easy to forget why something's been written the way it has.
For me, it's replaced a lot of conditional logic and manual iteration in timelines.