In this article my goal is to explain the concept and uses cases as I would have liked to learn it.
In its most basic (and admittedly useless) form:
1gsap.to('.my-element', {
2 y: function() {
3 // Simply sets the value as 10. No different than just putting y: 10
4 return 10;
5 }
6});
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 listtarget
— the actual DOM elementtargets
— the full array of all elements being animatedGoing 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.
1gsap.to('.lots-of-elements', {
2 height: function(index) {
3 // In this example, each element will be 10px taller than the last
4 return index * 10;
5 }
6});
Some simple ideas to get you started:
1gsap.to('.lots-of-elements', {
2 height: function(index) {
3 // In this example, each element will be 10px taller than the last
4 return index * 10;
5 },
6 stagger: 0.1 // And a simple stagger to make it fancy
7});
1gsap.to('.myFirstElement, .mySecondElement', {
2 scale: function(index, currentElement, allElements) {
3 // Check which element this is (useful for needle in a haystack scenarios)
4 if (currentElement === allElements[1]) {
5 return 10;
6 } else {
7 return 0;
8 }
9 }
10});
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.
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:
1let elements = document.querySelectorAll('.lots-of-elements');
2
3// Define a function that will take an index of an element
4// and return a value weighted on an ease
5let heightFn = gsap.utils.pipe(
6 gsap.utils.normalize(0, elements.length - 1),
7 gsap.parseEase("power2.out"),
8 gsap.utils.mapRange(0, 1, 0, elements.length * 10)
9);
10
11gsap.to(elements, {
12 height: heightFn
13 // Notice how we don't need to put the (index), because by default
14 // GSAP will pass all 3 values through if they can be.
15 // This means we can write our code in more modular ways.
16});
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.
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:
1gsap.from('.element', {
2 y: function(index, currentElement) {
3 // Dynamic attribute-powered values
4 return currentElement.getAttribute('data-gsap-y') || 0;
5 }
6});
This one's especially handy in Webflow sites, where dynamic fields can inject values into attributes.
Notice how I'm always declaring the function anonymously.
1y: function() {}
Rather than an arrow function
1y: ()=>{}
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
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.
Good projects get messy. Great projects get cleaned up. How should we handle technical debt and real-world refactoring?
A practical, code-heavy dive into GSAP’s utility functions—keyframes, pipe, clamp, normalize, and interpolate—and why they’re so much more than just shortcuts for animation math.
Exploring ways to keep JavaScript modular and maintainable in Webflow — from Slater to GitHub to a custom window.functions pattern. A look at what’s worked (and what hasn’t) while building more scalable websites.