Back home

Using Functions as Property Values in GSAP (And Why You Probably Should)

May 20, 2025

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:

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:

  1. index — the element’s index in the animation target list
  2. target — the actual DOM element
  3. targets — the full array of all elements being animated

Going forward I'll refer to them as follows for the sake of clarity

  1. index
  2. currentElement
  3. 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});

A Few Practical Examples

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.

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:

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.

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:

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.

Arrow functions?

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

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.

Writings

Launch day isn't the finish line

Good projects get messy. Great projects get cleaned up. How should we handle technical debt and real-world refactoring?

Useful GSAP utilities

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.

Organising JavaScript in Webflow: Exploring Scalable Patterns

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.

Building a Scroll-Based Image Sequencer with GSAP

An exploration in building a scroll-driven image sequence animation using GSAP and HTML5 canvas. Using Avif file compression with the Avif CLI, hosting strategies (Webflow vs AWS), GSAP and the quirks of working with canvas.