Opens in a new tab
  1. Home
  2. Guides
  3. Utilities
Utilities guide

GSAP in WordPress: getting started

Learn how to add GSAP to WordPress correctly, create your first tweens and timelines, use ScrollTrigger, manage script dependencies and build responsive, accessible animations.

  • Updated September 2, 2026
  • 21 min read
  • WordPress guide

GSAP in WordPress gives you precise control over animations, transitions, scroll effects and complex motion without requiring WordPress-specific animation APIs.

GSAP, short for GreenSock Animation Platform, is a JavaScript animation library. It can animate CSS properties, SVG elements, JavaScript values and many other properties while providing tools for sequencing, easing, scroll-driven effects and complex interactive interfaces.

WordPress does not change the way GSAP itself works. The important part is integrating the library correctly into WordPress’s script-loading system.

A solid setup looks like this:

WordPress
↓
enqueue GSAP Core
↓
enqueue optional GSAP plugins
↓
enqueue your animation script
↓
wait for the required DOM
↓
create animations

This guide covers the complete beginner workflow: what GSAP is, how to load it correctly in WordPress, how dependencies work, how to write your first tween and timeline, how to add ScrollTrigger, how to avoid common loading problems and how to keep animations performant and accessible.

What is GSAP?

GSAP is a JavaScript animation platform designed for programmatic motion on the web.

Instead of describing an animation entirely through CSS:

.box {
    transition: transform 1s ease;
}

you can control it through JavaScript:

gsap.to(".box", {
    x: 100,
    duration: 1
});

GSAP calculates and applies the intermediate property values required to move the element from its current state to the requested final state.

What can GSAP animate?

Common targets include:

  • position;
  • opacity;
  • scale;
  • rotation;
  • colors;
  • CSS transforms;
  • SVG properties;
  • numeric JavaScript values;
  • scroll-driven sequences;
  • complex groups of elements.

For example:

gsap.to(".card", {
    x: 120,
    opacity: 0.5,
    rotation: 15,
    duration: 1
});

can animate several properties at the same time.

GSAP is not a WordPress plugin

This distinction is useful.

GSAP itself is a JavaScript library:

gsap.min.js

WordPress is responsible for loading that JavaScript into the page.

After it is loaded, your animation code uses the normal GSAP API.

GSAP’s official installation documentation describes GSAP as framework-agnostic, meaning it can be used with WordPress, React and other web environments.

Understanding GSAP Core and plugins

GSAP is built around a core library plus optional plugins.

GSAP Core

The main library provides functionality such as:

  • gsap.to();
  • gsap.from();
  • gsap.fromTo();
  • gsap.timeline();
  • easing;
  • staggered animations;
  • callbacks;
  • animation controls.

For many animations, Core is all you need.

GSAP plugins

Plugins extend GSAP with additional capabilities.

Examples include:

  • ScrollTrigger;
  • ScrollSmoother;
  • SplitText;
  • Flip;
  • Draggable;
  • MotionPathPlugin;
  • DrawSVGPlugin;
  • MorphSVGPlugin.

One of the most commonly used plugins on WordPress websites is ScrollTrigger, which connects animations to scroll position.

GSAP 3.13 and newer

Current GSAP distribution is simpler than many older tutorials suggest. GSAP’s current installation documentation states that the private npm repository is no longer maintained and that GSAP and its plugins are available through the normal npm package from version 3.13 onward.

That means modern projects can simply use:

npm install gsap

when working with a build system.

The correct way to load GSAP in WordPress

WordPress has its own dependency-aware script system.

The recommended frontend hook is:

wp_enqueue_scripts

and the main function for loading JavaScript is:

wp_enqueue_script()

The official WordPress wp_enqueue_script() documentation describes this as the recommended method for adding JavaScript to a WordPress-generated page.

If you want a deeper explanation of WordPress’s loading system before continuing, see wp_enqueue_scripts explained.

Basic local GSAP setup

Suppose your theme contains:

/assets/js/gsap.min.js
/assets/js/app.js

You could enqueue them like this:

function mytheme_enqueue_animations() {

    wp_enqueue_script(
        'mytheme-gsap',
        get_theme_file_uri( '/assets/js/gsap.min.js' ),
        array(),
        '3.13.0',
        array(
            'in_footer' => true,
        )
    );

    wp_enqueue_script(
        'mytheme-animations',
        get_theme_file_uri( '/assets/js/app.js' ),
        array( 'mytheme-gsap' ),
        '1.0.0',
        array(
            'in_footer' => true,
        )
    );
}

add_action(
    'wp_enqueue_scripts',
    'mytheme_enqueue_animations'
);

The important part is:

array( 'mytheme-gsap' )

on the animation file.

That tells WordPress:

app.js
depends on
gsap.min.js

WordPress can therefore preserve the correct loading order.

Why dependencies matter

Your custom animation file may contain:

gsap.to(".hero", {
    opacity: 1
});

But the global:

gsap

object does not exist until GSAP Core has executed.

If the browser receives:

app.js
↓
GSAP Core

instead of:

GSAP Core
↓
app.js

you may receive:

ReferenceError:
gsap is not defined

Do not manage the order manually when WordPress can manage it

This:

array( 'mytheme-gsap' )

is safer than relying only on the order in which several unrelated enqueue functions happen to run.

WordPress maintains a dependency tree and loads registered dependencies before scripts that require them.

Loading GSAP from a CDN

You do not have to keep GSAP locally.

A CDN URL can also be passed to wp_enqueue_script().

Conceptually:

function mytheme_enqueue_animations() {

    wp_enqueue_script(
        'mytheme-gsap',
        'https://cdn.example.com/gsap.min.js',
        array(),
        '3.13.0',
        array(
            'in_footer' => true,
        )
    );

    wp_enqueue_script(
        'mytheme-animations',
        get_theme_file_uri( '/assets/js/app.js' ),
        array( 'mytheme-gsap' ),
        '1.0.0',
        array(
            'in_footer' => true,
        )
    );
}

add_action(
    'wp_enqueue_scripts',
    'mytheme_enqueue_animations'
);

The WordPress dependency model remains the same.

CDN or self-hosted?

Both approaches can work.

A CDN may provide:

  • easy setup;
  • edge delivery;
  • simple version selection.

A local copy provides:

  • greater deployment control;
  • fewer public third-party requests;
  • control over the exact file being served;
  • less dependency on a separate public CDN.

The dedicated comparison is covered in CDN vs. self-hosted assets in WordPress.

Your first GSAP animation in WordPress

Once GSAP is loaded, create an element to animate.

For example:

<div class="gsap-box">
    Animate me
</div>

Your CSS might contain:

.gsap-box {
    width: 120px;
    height: 120px;
}

Then inside:

app.js

you can write:

gsap.to(".gsap-box", {
    x: 200,
    duration: 1
});

The element moves 200 pixels along the X axis over one second.

Understanding the syntax

The first argument:

".gsap-box"

is the target.

The second argument:

{
    x: 200,
    duration: 1
}

is the configuration object.

It describes what should happen.

The basic mental model

gsap.to(
    target,
    properties
)

For example:

gsap.to(".logo", {
    scale: 1.2,
    rotation: 10,
    opacity: 0.8,
    duration: 0.8
});

gsap.to(), gsap.from() and gsap.fromTo()

These three methods cover a large percentage of everyday GSAP animations.

gsap.to()

Animate from the current state to another state.

gsap.to(".box", {
    x: 200,
    opacity: 0,
    duration: 1
});

Conceptually:

current position
↓
x: 200

current opacity
↓
opacity: 0

gsap.from()

Start from the supplied values and animate toward the element’s existing state.

gsap.from(".hero-title", {
    y: 40,
    opacity: 0,
    duration: 0.8
});

If the normal CSS state is:

y: 0
opacity: 1

the visual movement becomes:

y: 40
opacity: 0
↓
y: 0
opacity: 1

This is useful for entrance animations.

gsap.fromTo()

Define both states explicitly.

gsap.fromTo(
    ".card",
    {
        y: 60,
        opacity: 0
    },
    {
        y: 0,
        opacity: 1,
        duration: 0.8
    }
);

This is useful when you want complete control over both endpoints.

Easing in GSAP

Without easing, movement can feel mechanical because the animation progresses at a constant mathematical rate.

Easing controls how the speed changes throughout the animation.

A simple example

gsap.to(".box", {
    x: 300,
    duration: 1,
    ease: "power2.out"
});

The animation moves quickly at first and slows toward the end.

Common GSAP easing families

Examples include:

  • power1;
  • power2;
  • power3;
  • power4;
  • expo;
  • back;
  • bounce;
  • elastic.

Many support directions such as:

power3.in
power3.out
power3.inOut

Do not choose easing only because it looks dramatic in isolation. Motion should match the interface. Navigation, form feedback and page transitions usually benefit from controlled movement rather than theatrical bouncing.

Animating multiple elements with stagger

Suppose you have:

<div class="card">One</div>
<div class="card">Two</div>
<div class="card">Three</div>

You could animate them together:

gsap.from(".card", {
    y: 30,
    opacity: 0,
    duration: 0.6
});

Or introduce a delay between each element:

gsap.from(".card", {
    y: 30,
    opacity: 0,
    duration: 0.6,
    stagger: 0.12
});

The sequence becomes approximately:

card 1
↓ 0.12s
card 2
↓ 0.12s
card 3

Stagger is especially useful for:

  • navigation links;
  • cards;
  • lists;
  • gallery items;
  • hero text fragments.

Using GSAP timelines in WordPress

Individual tweens are useful, but real interfaces often need several animations to work together.

A GSAP timeline is a container that sequences multiple tweens.

Basic timeline

const tl = gsap.timeline();

tl.from(".hero-title", {
    y: 40,
    opacity: 0,
    duration: 0.7
});

tl.from(".hero-text", {
    y: 20,
    opacity: 0,
    duration: 0.5
});

tl.from(".hero-button", {
    scale: 0.9,
    opacity: 0,
    duration: 0.4
});

The default sequence is:

title
↓
text
↓
button

Why timelines are preferable to manual delays

You could write:

delay: 0
delay: 0.7
delay: 1.2

on separate animations.

But that becomes fragile when durations change.

A timeline understands relationships between animations.

You can also control the whole sequence:

tl.play();
tl.pause();
tl.reverse();
tl.restart();

Timeline defaults

You can avoid repeating common settings:

const tl = gsap.timeline({
    defaults: {
        duration: 0.7,
        ease: "power3.out"
    }
});

tl.from(".hero-title", {
    y: 40,
    opacity: 0
});

tl.from(".hero-text", {
    y: 20,
    opacity: 0
});

Waiting for WordPress content before animating it

One of the most common beginner problems is trying to animate an element before it exists.

DOMContentLoaded

If your script might execute before the DOM is ready:

document.addEventListener(
    "DOMContentLoaded",
    () => {

        gsap.from(".hero-title", {
            y: 30,
            opacity: 0,
            duration: 0.8
        });

    }
);

This waits until the HTML document has been parsed.

Do you always need DOMContentLoaded?

No.

If your script is loaded in the footer or deferred appropriately, the required elements may already be parsed by the time the script executes.

But explicit initialization is often useful in larger projects because it creates a predictable entry point.

DOMContentLoaded and window load are different

DOMContentLoaded waits for the HTML structure.

window.load waits for additional resources such as images to finish loading.

Do not wait for every image on the website simply because you want to animate a heading.

Use the minimum lifecycle event your animation actually needs.

Loading GSAP plugins correctly

ScrollTrigger and other plugins must load after GSAP Core.

Then your animation code must load after the plugin it uses.

Example with ScrollTrigger

function mytheme_enqueue_animations() {

    wp_enqueue_script(
        'mytheme-gsap',
        get_theme_file_uri(
            '/assets/js/gsap.min.js'
        ),
        array(),
        '3.13.0',
        array(
            'in_footer' => true,
        )
    );

    wp_enqueue_script(
        'mytheme-scrolltrigger',
        get_theme_file_uri(
            '/assets/js/ScrollTrigger.min.js'
        ),
        array( 'mytheme-gsap' ),
        '3.13.0',
        array(
            'in_footer' => true,
        )
    );

    wp_enqueue_script(
        'mytheme-animations',
        get_theme_file_uri(
            '/assets/js/app.js'
        ),
        array(
            'mytheme-gsap',
            'mytheme-scrolltrigger'
        ),
        '1.0.0',
        array(
            'in_footer' => true,
        )
    );
}

add_action(
    'wp_enqueue_scripts',
    'mytheme_enqueue_animations'
);

The dependency tree becomes:

GSAP Core
↓
ScrollTrigger
↓
app.js

Register the plugin

Inside your JavaScript:

gsap.registerPlugin(
    ScrollTrigger
);

GSAP’s official documentation recommends explicit plugin registration. This is particularly important in module and build environments, where unused code can otherwise be removed during tree shaking.

Your first ScrollTrigger animation

ScrollTrigger lets an animation react to scroll position.

The official ScrollTrigger documentation covers triggers, start and end positions, scrub, pinning, callbacks and other scroll controls.

Simple reveal

gsap.registerPlugin(
    ScrollTrigger
);

gsap.from(".section-title", {
    y: 50,
    opacity: 0,
    duration: 0.8,
    scrollTrigger: {
        trigger: ".section-title",
        start: "top 80%"
    }
});

This means approximately:

when top of .section-title
reaches 80% down viewport
↓
start animation

The trigger is not necessarily the animated element

You can use:

trigger: ".services-section"

while animating:

".service-card"

For example:

gsap.from(".service-card", {
    y: 40,
    opacity: 0,
    stagger: 0.1,
    scrollTrigger: {
        trigger: ".services-section",
        start: "top 75%"
    }
});

Understanding start positions

A ScrollTrigger start value such as:

start: "top 80%"

compares two points.

top
↑
trigger element

80%
↑
viewport

The animation starts when those positions meet.

Another common example

start: "top center"

means:

top of trigger
reaches
center of viewport

Use markers while developing

ScrollTrigger supports:

markers: true

during development:

scrollTrigger: {
    trigger: ".section",
    start: "top 80%",
    markers: true
}

This displays visual markers showing the trigger positions.

Remove them before production.

Scroll-linked animations with scrub

A normal ScrollTrigger can simply start an animation when the trigger point is reached.

With:

scrub: true

animation progress becomes linked to scroll progress.

Example

gsap.to(".image", {
    y: -100,
    scrollTrigger: {
        trigger: ".image-section",
        start: "top bottom",
        end: "bottom top",
        scrub: true
    }
});

Conceptually:

scroll begins
↓
animation progresses

scroll stops
↓
animation stops

scroll reverses
↓
animation reverses

This can be useful for:

  • parallax effects;
  • progress indicators;
  • image movement;
  • storytelling sections.

Avoid attaching scrubbed motion to every visible element. Scroll-linked animation is useful when it supports hierarchy or interaction, not when the entire page appears to be resisting the user’s mouse wheel.

Pinning sections with ScrollTrigger

ScrollTrigger can temporarily pin an element while scrolling.

ScrollTrigger.create({
    trigger: ".feature",
    start: "top top",
    end: "+=1000",
    pin: true
});

This can create experiences such as:

section enters viewport
↓
section stays fixed
↓
scroll drives content
↓
section releases

Pinning changes layout behavior

GSAP may need to introduce spacing so surrounding content continues correctly.

Always test pinned layouts across:

  • desktop;
  • tablet;
  • mobile;
  • different viewport heights.

A pinned section designed only on a 27-inch monitor has a remarkable ability to become architectural rubble on a 375-pixel phone.

Use transform properties for smooth animation

For many visual movements, prefer transform-based properties such as:

  • x;
  • y;
  • xPercent;
  • yPercent;
  • scale;
  • rotation.

Prefer this

gsap.to(".box", {
    x: 100
});

over repeatedly animating layout properties when unnecessary

gsap.to(".box", {
    left: "100px"
});

Transform animations usually avoid repeatedly recalculating surrounding document layout.

Opacity is also commonly efficient

gsap.from(".card", {
    opacity: 0,
    y: 20
});

This combination is a good default for many entrance animations.

Avoid animating too much at once

GSAP is highly optimized, but it cannot make unlimited browser work free.

If you animate:

500 DOM nodes
+
large filters
+
multiple blurred backgrounds
+
continuous scroll effects
+
video
+
canvas

the browser still has to render everything.

Performance depends on the entire rendering pipeline

Consider:

  • layout;
  • paint;
  • compositing;
  • image size;
  • DOM size;
  • other JavaScript;
  • mobile hardware.

The existence of GSAP does not repeal physics.

Load GSAP only where you need it

A common WordPress mistake is loading animation libraries across every frontend page even when only the homepage uses them.

Conditional enqueueing

You can load GSAP only on the homepage:

function mytheme_enqueue_animations() {

    if ( ! is_front_page() ) {
        return;
    }

    wp_enqueue_script(
        'mytheme-gsap',
        get_theme_file_uri(
            '/assets/js/gsap.min.js'
        ),
        array(),
        '3.13.0',
        array(
            'in_footer' => true,
        )
    );

    wp_enqueue_script(
        'mytheme-animations',
        get_theme_file_uri(
            '/assets/js/home.js'
        ),
        array( 'mytheme-gsap' ),
        '1.0.0',
        array(
            'in_footer' => true,
        )
    );
}

add_action(
    'wp_enqueue_scripts',
    'mytheme_enqueue_animations'
);

This avoids sending animation code to pages that do not use it.

You can target other WordPress conditions

Examples include:

is_page()
is_single()
is_singular()
is_archive()
is_page_template()

Conditional loading is especially useful when plugins or themes contain several independent animation experiences.

Do not edit a parent theme directly

If you purchased or installed a theme that receives updates, putting custom GSAP code directly into its files can be risky.

An update may overwrite:

functions.php
app.js
custom templates

The official GSAP WordPress guidance also warns against modifying regularly updated parent themes directly.

Safer options include

  • a child theme;
  • a custom plugin;
  • a dedicated code-management system;
  • a library-management module.

For reusable site functionality, a plugin is often preferable to theme-specific code.

Using GSAP through TheOneWP Library Importer

TheOneWP Library Importer can manage frontend libraries without requiring every integration to be hardcoded into functions.php.

The verified module includes GSAP Core among its library catalog options and can load configured libraries through different delivery modes.

Available loading approaches include

  • the configured CDN source;
  • a downloaded local static copy;
  • inline loading where appropriate.

This makes it possible to choose:

GSAP
↓
CDN

or:

GSAP
↓
local static file

without changing the animation logic itself.

The architectural tradeoffs are covered in CDN vs. self-hosted assets in WordPress.

Using GSAP with npm and a WordPress build process

More advanced WordPress themes and plugins often use npm, Vite, webpack or another build system.

Install GSAP:

npm install gsap

Then import it:

import { gsap } from "gsap";

ScrollTrigger with modules

import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";

gsap.registerPlugin(
    ScrollTrigger
);

Then:

gsap.from(".section-title", {
    y: 40,
    opacity: 0,
    scrollTrigger: {
        trigger: ".section-title",
        start: "top 80%"
    }
});

Your build tool creates the final bundle

The development flow becomes:

source JavaScript
↓
npm dependencies
↓
build tool
↓
dist/app.js
↓
WordPress enqueue
↓
browser

WordPress does not need to understand how GSAP was bundled internally.

It only needs to enqueue the final browser asset.

Using defer and async carefully

Modern WordPress supports script-loading strategies through wp_enqueue_script().

For example:

array(
    'strategy'  => 'defer',
    'in_footer' => true,
)

WordPress’s dependency system considers script relationships when selecting an eligible loading strategy.

defer preserves execution order

Deferred scripts execute after HTML parsing and maintain their document order.

This makes defer more compatible with dependency chains such as:

GSAP
↓
ScrollTrigger
↓
app.js

async does not guarantee dependency order

Asynchronous scripts execute when each file becomes available.

That means:

app.js

could theoretically execute before:

gsap.min.js

if the loading architecture is not managed correctly.

Do not add async indiscriminately to scripts that depend on each other.

Responsive GSAP animations

An animation that works well on desktop may be inappropriate on mobile.

GSAP provides:

gsap.matchMedia()

for creating responsive animation contexts.

Example

const mm = gsap.matchMedia();

mm.add(
    "(min-width: 768px)",
    () => {

        gsap.from(".hero-image", {
            x: 100,
            opacity: 0,
            duration: 1
        });

    }
);

The animation is created only when the media query matches.

Mobile can use a simpler animation

mm.add(
    "(max-width: 767px)",
    () => {

        gsap.from(".hero-image", {
            opacity: 0,
            duration: 0.5
        });

    }
);

This is often better than forcing identical motion across every device.

Respect prefers-reduced-motion

Animation is not purely decorative from an accessibility perspective.

Some users request reduced motion through their operating system or browser.

You can detect that preference with:

window.matchMedia(
    "(prefers-reduced-motion: reduce)"
).matches;

Simple approach

const reduceMotion =
    window.matchMedia(
        "(prefers-reduced-motion: reduce)"
    ).matches;

if ( ! reduceMotion ) {

    gsap.from(".hero-title", {
        y: 40,
        opacity: 0,
        duration: 0.8
    });

}

Do not hide essential content behind animation

The page should remain usable if animation is:

  • disabled;
  • reduced;
  • blocked;
  • unable to initialize.

A heading should not permanently remain:

opacity: 0

just because JavaScript failed.

Avoid flashes of invisible content

Entrance animations frequently start with:

opacity: 0

If you put that state directly into normal CSS:

.hero-title {
    opacity: 0;
}

and JavaScript fails, the content may remain invisible.

A safer strategy

Let normal HTML and CSS remain visible by default.

Then let GSAP establish the starting state when animation initializes:

gsap.from(".hero-title", {
    opacity: 0,
    y: 30,
    duration: 0.8
});

For more complex page-load transitions, progressively enhance the page with a JavaScript-enabled class and provide a fallback.

Selectors and WordPress-generated content

WordPress themes, blocks and page builders can generate repeated classes.

If you write:

gsap.to(".title", {
    opacity: 0
});

you may accidentally target every:

.title

on the page.

Scope selectors

Prefer:

gsap.from(
    ".homepage-hero .title",
    {
        opacity: 0,
        y: 30
    }
);

or select a parent first:

const hero =
    document.querySelector(
        ".homepage-hero"
    );

if ( hero ) {

    gsap.from(
        hero.querySelector(".title"),
        {
            opacity: 0,
            y: 30
        }
    );

}

This reduces accidental collisions with theme and plugin markup.

Always guard optional WordPress elements

Templates are dynamic.

A homepage may have:

.hero

while another page does not.

A safe pattern

const hero =
    document.querySelector(
        ".homepage-hero"
    );

if ( hero ) {

    gsap.from(hero, {
        opacity: 0,
        duration: 0.8
    });

}

This is especially important when one animation bundle is reused across multiple WordPress templates.

Refreshing ScrollTrigger after layout changes

ScrollTrigger calculates positions based on page layout.

That layout can change when:

  • images load;
  • fonts change metrics;
  • accordions expand;
  • AJAX content appears;
  • page-builder elements resize.

When necessary, you can request recalculation:

ScrollTrigger.refresh();

Do not call refresh continuously

Refreshing trigger geometry requires browser work.

Use it when a meaningful layout change occurs, not inside every scroll event.

Common GSAP mistakes in WordPress

GSAP is not defined

Usually means your animation code executed before GSAP Core or GSAP failed to load.

Check:

  • Network panel;
  • script dependency handles;
  • browser console;
  • enqueue order.

ScrollTrigger is not defined

Core may be loaded while the plugin is missing.

Verify:

GSAP
↓
ScrollTrigger
↓
app.js

Forgetting registerPlugin()

Use:

gsap.registerPlugin(
    ScrollTrigger
);

especially with module builds.

Hardcoding scripts into header.php

This bypasses WordPress’s dependency management and makes duplicate loading easier.

Use wp_enqueue_script() where possible.

Loading GSAP twice

A theme may load GSAP while a plugin or page builder loads another copy.

Inspect the Network panel before adding another instance.

Loading multiple GSAP versions

A page containing:

GSAP 3.x
+
another GSAP 3.x build

can create unnecessary bandwidth and difficult debugging.

Loading plugins before Core

Use WordPress dependencies rather than hoping file order remains stable.

Using async on dependent scripts

Asynchronous execution does not preserve predictable execution order.

Editing the parent theme

Updates can remove your work.

Animating nonexistent elements

Use existence checks and scoped initialization.

Waiting for window load unnecessarily

A simple heading reveal usually does not need to wait for every image on the site.

Animating layout-heavy properties constantly

Prefer transforms where appropriate.

Using ScrollTrigger for every element

Not every paragraph needs its own animation controller.

Ignoring reduced-motion preferences

Motion should degrade gracefully.

Making content depend on JavaScript animation

Essential content should remain accessible even if animation fails.

Leaving ScrollTrigger markers enabled

Use them for debugging, then remove them from production.

A clean starter architecture

A simple theme structure might be:

my-theme/
│
├── functions.php
│
└── assets/
    └── js/
        ├── gsap.min.js
        ├── ScrollTrigger.min.js
        └── animations.js

functions.php

function mytheme_enqueue_gsap() {

    wp_enqueue_script(
        'mytheme-gsap',
        get_theme_file_uri(
            '/assets/js/gsap.min.js'
        ),
        array(),
        '3.13.0',
        array(
            'in_footer' => true,
        )
    );

    wp_enqueue_script(
        'mytheme-scrolltrigger',
        get_theme_file_uri(
            '/assets/js/ScrollTrigger.min.js'
        ),
        array(
            'mytheme-gsap'
        ),
        '3.13.0',
        array(
            'in_footer' => true,
        )
    );

    wp_enqueue_script(
        'mytheme-animations',
        get_theme_file_uri(
            '/assets/js/animations.js'
        ),
        array(
            'mytheme-gsap',
            'mytheme-scrolltrigger'
        ),
        '1.0.0',
        array(
            'in_footer' => true,
        )
    );
}

add_action(
    'wp_enqueue_scripts',
    'mytheme_enqueue_gsap'
);

animations.js

document.addEventListener(
    "DOMContentLoaded",
    () => {

        gsap.registerPlugin(
            ScrollTrigger
        );

        const hero =
            document.querySelector(
                ".homepage-hero"
            );

        if ( hero ) {

            const tl =
                gsap.timeline({
                    defaults: {
                        ease: "power3.out"
                    }
                });

            tl.from(
                hero.querySelector("h1"),
                {
                    y: 40,
                    opacity: 0,
                    duration: 0.8
                }
            );

            tl.from(
                hero.querySelector("p"),
                {
                    y: 20,
                    opacity: 0,
                    duration: 0.6
                },
                "-=0.4"
            );
        }

        gsap.utils
            .toArray(".reveal-section")
            .forEach((section) => {

                gsap.from(section, {
                    y: 30,
                    opacity: 0,
                    duration: 0.7,
                    scrollTrigger: {
                        trigger: section,
                        start: "top 80%"
                    }
                });

            });

    }
);

This gives you a maintainable foundation:

dependency-managed loading
+
central initialization
+
element guards
+
timeline
+
ScrollTrigger

A practical learning path for GSAP in WordPress

Do not begin by trying to recreate an award-site homepage with eleven pinned sections and a cursor that behaves like an anxious jellyfish.

Learn in this order:

  1. Load GSAP correctly.
  2. Animate one element with gsap.to().
  3. Learn from() and fromTo().
  4. Understand transforms.
  5. Learn easing.
  6. Animate groups with stagger.
  7. Create a timeline.
  8. Add ScrollTrigger.
  9. Learn trigger start and end positions.
  10. Experiment with scrub.
  11. Learn responsive animation with matchMedia().
  12. Add reduced-motion handling.
  13. Optimize and conditionally load scripts.

Once those concepts are comfortable, more sophisticated GSAP interfaces become combinations of familiar primitives rather than mysterious blocks of animation code.

GSAP in WordPress checklist

  • Load GSAP through WordPress’s enqueue system.
  • Use wp_enqueue_scripts for frontend scripts.
  • Give every script a unique handle.
  • Declare dependencies explicitly.
  • Load GSAP Core before its plugins.
  • Load animation code after the libraries it uses.
  • Register GSAP plugins explicitly.
  • Pin or track the GSAP version you deploy.
  • Choose CDN or local delivery deliberately.
  • Avoid loading duplicate GSAP copies.
  • Do not modify an updateable parent theme directly.
  • Use gsap.to(), from() and fromTo() appropriately.
  • Use timelines for coordinated sequences.
  • Use stagger for related groups of elements.
  • Prefer transform-based movement where appropriate.
  • Use ScrollTrigger only where scroll interaction adds value.
  • Use markers while debugging ScrollTrigger.
  • Remove markers in production.
  • Conditionally enqueue GSAP when only some pages need it.
  • Scope selectors to avoid theme and plugin collisions.
  • Check that optional elements exist before initializing them.
  • Respect prefers-reduced-motion.
  • Keep content usable without animation.
  • Test mobile and tablet layouts.
  • Test performance on actual lower-powered devices.
  • Use ScrollTrigger.refresh() when meaningful layout changes require recalculation.
  • Avoid excessive animation simply because the library makes it possible.

Related WordPress frontend guides

Continue with these related guides and tools:

Final thoughts

Getting started with GSAP in WordPress requires understanding two separate systems.

First:

WordPress
↓
loads and manages
JavaScript dependencies

Second:

GSAP
↓
creates and controls
the animations

Keep those responsibilities separate.

Use WordPress to load:

GSAP Core
↓
optional GSAP plugins
↓
your animation code

Then use GSAP to build motion through:

tweens
+
timelines
+
easing
+
stagger
+
ScrollTrigger

Start with simple transforms and entrance effects before moving into pinned sections and complex scroll-driven sequences.

Declare dependencies correctly, avoid duplicate libraries, scope your selectors and load GSAP only on pages that actually need it.

Most importantly, treat motion as part of the interface rather than decoration added after the page is finished. A useful animation clarifies hierarchy, provides feedback or guides attention. It should not make users wait for the website to finish performing.

If you want to manage GSAP and other frontend libraries without manually maintaining every enqueue declaration, TheOneWP Library Importer provides GSAP Core as part of its library catalog and lets you choose between CDN delivery, a downloaded local static copy or inline loading depending on the architecture you need.

Simplify your WordPress stack

A modular WordPress toolkit. 104 focused tools.

Ultimately, you can build cleaner workflows, maintain fewer plugins and enable only the features each website actually needs.