Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
aiskillstore avatar

Gsap Animations

  • 2 installs
  • 404 repo stars
  • Updated August 5, 2026
  • aiskillstore/marketplace

gsap-animations is a Claude Code skill of GSAP best practices for building performant, accessible web animations like scroll triggers, parallax, and reveals.

About

gsap-animations is a Claude Code skill covering GSAP animation best practices for web design. A developer uses it to implement scroll-triggered, staggered, parallax, and hero-timeline animations while keeping them performant and accessible. It stresses animating only transform and opacity, respecting prefers-reduced-motion, and providing responsive, JS-optional fallbacks, including a WordPress enqueue path.

  • GSAP and ScrollTrigger patterns for scroll, stagger, and parallax
  • Performance rules: animate transform/opacity for 60fps
  • Accessibility via prefers-reduced-motion and CSS fallbacks

Gsap Animations by the numbers

  • 2 all-time installs (skills.sh)
  • Ranked #1,862 of 2,245 Frontend Development skills by installs in the Skillselion catalog
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
At a glance

gsap-animations capabilities & compatibility

Capabilities
scroll animation · parallax · reduced motion · responsive animation
Use cases
frontend · ui design · web design
From the docs

What gsap-animations says it does

GSAP animation best practices for web design - scroll triggers, performance optimization, accessibility, responsive animations, and testing integration.
SKILL.md
Animate `transform` and `opacity` only (GPU-accelerated)
SKILL.md
npx skills add https://github.com/aiskillstore/marketplace --skill gsap-animations

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs2
repo stars404
Last updatedAugust 5, 2026
Repositoryaiskillstore/marketplace

What it does

Implement performant, accessible GSAP scroll and reveal animations on web pages.

Who is it for?

Adding performant, accessible GSAP animations to web pages, including WordPress themes.

Skip if: Non-web animation, video editing, or backend work.

When should I use this skill?

When implementing or reviewing GSAP/ScrollTrigger animations on a web project.

Files

SKILL.mdMarkdownGitHub ↗

GSAP Animation Best Practices

Comprehensive guide for implementing professional, accessible, and performant animations using GSAP (GreenSock Animation Platform).

Core Principles

1. Performance First

  • Animate transform and opacity only (GPU-accelerated)
  • Avoid animating width, height, top, left, margin, padding
  • Use will-change sparingly
  • Target 60fps on all devices

2. Accessibility Always

  • Respect prefers-reduced-motion
  • Ensure content is visible without JavaScript
  • Don't hide critical content behind animations
  • Provide skip/pause controls for long animations

3. Progressive Enhancement

  • Content must work without animations
  • Animations enhance, not replace, functionality
  • Test with animations disabled

---

GSAP Setup

Installation

<!-- CDN (recommended for WordPress) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js"></script>

<!-- Optional plugins -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollSmoother.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/SplitText.min.js"></script>

WordPress Enqueue

function theme_enqueue_gsap() {
    // GSAP Core
    wp_enqueue_script(
        'gsap',
        'https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js',
        array(),
        '3.12.5',
        true
    );

    // ScrollTrigger
    wp_enqueue_script(
        'gsap-scrolltrigger',
        'https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js',
        array('gsap'),
        '3.12.5',
        true
    );

    // Theme animations
    wp_enqueue_script(
        'theme-animations',
        get_theme_file_uri('/assets/js/animations.js'),
        array('gsap', 'gsap-scrolltrigger'),
        filemtime(get_theme_file_path('/assets/js/animations.js')),
        true
    );
}
add_action('wp_enqueue_scripts', 'theme_enqueue_gsap');

---

Animation Patterns

1. Fade In on Scroll

// Basic fade in
gsap.from('.fade-in', {
    opacity: 0,
    y: 50,
    duration: 1,
    stagger: 0.2,
    scrollTrigger: {
        trigger: '.fade-in',
        start: 'top 80%',
        toggleActions: 'play none none none'
    }
});

2. Staggered Elements

// Cards appearing one by one
gsap.from('.card', {
    opacity: 0,
    y: 100,
    duration: 0.8,
    stagger: {
        amount: 0.6,
        from: 'start'
    },
    ease: 'power2.out',
    scrollTrigger: {
        trigger: '.cards-container',
        start: 'top 75%'
    }
});

3. Parallax Effect

// Subtle parallax on images
gsap.to('.parallax-image', {
    yPercent: -20,
    ease: 'none',
    scrollTrigger: {
        trigger: '.parallax-section',
        start: 'top bottom',
        end: 'bottom top',
        scrub: true
    }
});

4. Text Reveal (Line by Line)

// Requires SplitText plugin (Club GreenSock)
// Or use CSS-based alternative below

// CSS Alternative - wrap each line in a span
gsap.from('.reveal-line', {
    opacity: 0,
    y: '100%',
    duration: 0.8,
    stagger: 0.1,
    ease: 'power3.out',
    scrollTrigger: {
        trigger: '.text-reveal',
        start: 'top 80%'
    }
});

5. Curtain/Mask Reveal

// Image revealed by sliding mask
gsap.to('.curtain-mask', {
    scaleX: 0,
    transformOrigin: 'right center',
    duration: 1.2,
    ease: 'power4.inOut',
    scrollTrigger: {
        trigger: '.curtain-container',
        start: 'top 70%'
    }
});

6. Hero Animation Timeline

// Complex hero sequence
const heroTL = gsap.timeline({
    defaults: { ease: 'power3.out' }
});

heroTL
    .from('.hero-bg', { scale: 1.2, duration: 1.5 })
    .from('.hero-title', { opacity: 0, y: 100, duration: 1 }, '-=1')
    .from('.hero-subtitle', { opacity: 0, y: 50, duration: 0.8 }, '-=0.5')
    .from('.hero-cta', { opacity: 0, y: 30, duration: 0.6 }, '-=0.3');

---

Accessibility

Respect Reduced Motion

// Check user preference
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

// Option 1: Disable all animations
if (prefersReducedMotion) {
    gsap.globalTimeline.timeScale(0);
    ScrollTrigger.getAll().forEach(st => st.kill());
}

// Option 2: Simplified animations
const animationConfig = prefersReducedMotion
    ? { duration: 0, stagger: 0 }
    : { duration: 1, stagger: 0.2 };

gsap.from('.element', {
    opacity: 0,
    y: prefersReducedMotion ? 0 : 50,
    ...animationConfig
});

CSS Fallback

/* Ensure content visible without JS */
.fade-in {
    opacity: 1;
    transform: translateY(0);
}

/* Only hide if animations will run */
.js .fade-in {
    opacity: 0;
    transform: translateY(50px);
}

/* Respect reduced motion in CSS too */
@media (prefers-reduced-motion: reduce) {
    .js .fade-in {
        opacity: 1;
        transform: none;
    }
}

Add JS Class to HTML

// Add at start of script
document.documentElement.classList.add('js');

---

Responsive Animations

Breakpoint-Aware Animations

// Create responsive animations
const mm = gsap.matchMedia();

mm.add('(min-width: 1024px)', () => {
    // Desktop animations
    gsap.from('.hero-image', {
        x: 100,
        opacity: 0,
        duration: 1.2
    });

    return () => {
        // Cleanup on breakpoint change
    };
});

mm.add('(max-width: 1023px)', () => {
    // Mobile animations (simpler)
    gsap.from('.hero-image', {
        opacity: 0,
        duration: 0.8
    });
});

Refresh on Resize

// Recalculate ScrollTrigger on resize
let resizeTimer;
window.addEventListener('resize', () => {
    clearTimeout(resizeTimer);
    resizeTimer = setTimeout(() => {
        ScrollTrigger.refresh();
    }, 250);
});

---

Performance Optimization

1. Use Transform Properties Only

// GOOD - GPU accelerated
gsap.to('.element', {
    x: 100,          // transform: translateX
    y: 50,           // transform: translateY
    rotation: 45,    // transform: rotate
    scale: 1.2,      // transform: scale
    opacity: 0.5
});

// BAD - Causes layout/paint
gsap.to('.element', {
    left: 100,       // Triggers layout
    width: '200px',  // Triggers layout
    marginTop: 50    // Triggers layout
});

2. Batch Similar Animations

// Use batch for many similar elements
ScrollTrigger.batch('.card', {
    onEnter: batch => gsap.to(batch, {
        opacity: 1,
        y: 0,
        stagger: 0.1
    }),
    start: 'top 85%'
});

3. Kill Unused ScrollTriggers

// Cleanup when navigating (SPA) or component unmount
function cleanup() {
    ScrollTrigger.getAll().forEach(st => st.kill());
    gsap.killTweensOf('*');
}

4. Lazy Initialize

// Only initialize animations for visible sections
const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            initSectionAnimations(entry.target);
            observer.unobserve(entry.target);
        }
    });
}, { rootMargin: '100px' });

document.querySelectorAll('.animated-section').forEach(section => {
    observer.observe(section);
});

---

ScrollTrigger Best Practices

1. Proper Start/End Points

// Avoid common mistakes
ScrollTrigger.create({
    trigger: '.section',
    start: 'top 80%',    // When top of trigger hits 80% from top of viewport
    end: 'bottom 20%',   // When bottom of trigger hits 20% from top
    markers: true,       // Debug only - remove in production!
});

2. Pin Sections Carefully

// Pinning can cause layout issues
ScrollTrigger.create({
    trigger: '.pinned-section',
    start: 'top top',
    end: '+=100%',
    pin: true,
    pinSpacing: true,    // Usually want this true
    anticipatePin: 1     // Helps with mobile
});

3. Handle Images Loading

// Wait for images before calculating positions
ScrollTrigger.config({
    ignoreMobileResize: true
});

window.addEventListener('load', () => {
    ScrollTrigger.refresh();
});

// Or refresh after lazy images load
document.querySelectorAll('img[loading="lazy"]').forEach(img => {
    img.addEventListener('load', () => ScrollTrigger.refresh());
});

---

Testing Integration

Visual QA Compatibility

For the visual-qa skill to capture animations correctly:

// Expose function to complete all animations instantly
window.completeAllAnimations = function() {
    gsap.globalTimeline.progress(1);
    ScrollTrigger.getAll().forEach(st => {
        st.scroll(st.end);
    });
};

// Or skip animations entirely for screenshots
if (window.location.search.includes('skip-animations')) {
    gsap.globalTimeline.timeScale(100);
}

Playwright Testing

// In Playwright test
await page.evaluate(() => {
    if (window.completeAllAnimations) {
        window.completeAllAnimations();
    }
});
await page.waitForTimeout(500);
await page.screenshot({ path: 'screenshot.png', fullPage: true });

---

Common Animation Library

Reusable Animation Classes

// animations.js - Reusable animation library

const Animations = {
    // Initialize all animations
    init() {
        if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
            return;
        }

        this.fadeIn();
        this.slideIn();
        this.parallax();
        this.textReveal();
    },

    fadeIn() {
        gsap.utils.toArray('[data-animate="fade-in"]').forEach(el => {
            gsap.from(el, {
                opacity: 0,
                y: 50,
                duration: 0.8,
                scrollTrigger: {
                    trigger: el,
                    start: 'top 85%',
                    once: true
                }
            });
        });
    },

    slideIn() {
        gsap.utils.toArray('[data-animate="slide-left"]').forEach(el => {
            gsap.from(el, {
                opacity: 0,
                x: -100,
                duration: 1,
                scrollTrigger: {
                    trigger: el,
                    start: 'top 80%',
                    once: true
                }
            });
        });

        gsap.utils.toArray('[data-animate="slide-right"]').forEach(el => {
            gsap.from(el, {
                opacity: 0,
                x: 100,
                duration: 1,
                scrollTrigger: {
                    trigger: el,
                    start: 'top 80%',
                    once: true
                }
            });
        });
    },

    parallax() {
        gsap.utils.toArray('[data-parallax]').forEach(el => {
            const speed = el.dataset.parallax || 0.2;
            gsap.to(el, {
                yPercent: -100 * speed,
                ease: 'none',
                scrollTrigger: {
                    trigger: el.parentElement,
                    start: 'top bottom',
                    end: 'bottom top',
                    scrub: true
                }
            });
        });
    },

    textReveal() {
        gsap.utils.toArray('[data-animate="text-reveal"]').forEach(el => {
            const lines = el.querySelectorAll('.line');
            gsap.from(lines, {
                opacity: 0,
                y: '100%',
                duration: 0.8,
                stagger: 0.1,
                scrollTrigger: {
                    trigger: el,
                    start: 'top 80%',
                    once: true
                }
            });
        });
    },

    // Refresh after dynamic content
    refresh() {
        ScrollTrigger.refresh();
    },

    // Cleanup for SPA navigation
    destroy() {
        ScrollTrigger.getAll().forEach(st => st.kill());
        gsap.killTweensOf('*');
    }
};

// Initialize on DOM ready
document.addEventListener('DOMContentLoaded', () => Animations.init());

HTML Usage

<!-- Fade in -->
<div data-animate="fade-in">Content</div>

<!-- Slide from left -->
<div data-animate="slide-left">Content</div>

<!-- Parallax (0.2 = 20% speed) -->
<img data-parallax="0.3" src="image.jpg">

<!-- Text reveal (requires line wrapping) -->
<div data-animate="text-reveal">
    <div class="line">First line</div>
    <div class="line">Second line</div>
</div>

---

Debugging

Enable Markers

ScrollTrigger.defaults({
    markers: true  // Shows start/end markers
});

Log Animation Events

gsap.to('.element', {
    x: 100,
    onStart: () => console.log('Animation started'),
    onComplete: () => console.log('Animation completed'),
    onUpdate: self => console.log('Progress:', self.progress())
});

Check for Issues

// List all ScrollTriggers
console.log('ScrollTriggers:', ScrollTrigger.getAll());

// Check if element exists
const el = document.querySelector('.animated-element');
if (!el) console.warn('Animation target not found!');

---

Checklist

Before Launch

  • [ ] Remove all markers: true
  • [ ] Test with prefers-reduced-motion: reduce
  • [ ] Test on mobile devices (real devices, not just DevTools)
  • [ ] Check performance in DevTools Performance tab
  • [ ] Verify 60fps on target devices
  • [ ] Content visible without JavaScript
  • [ ] Images lazy-loaded before ScrollTrigger refresh
  • [ ] No layout thrashing (avoid animating layout properties)

Visual QA Integration

  • [ ] Animations complete before screenshots
  • [ ] Full-page scroll triggers all animations
  • [ ] Screenshots capture final animated state
  • [ ] Test at all viewport sizes

---

Resources

Related skills

FAQ

Which properties should I animate with GSAP for performance?

Animate transform and opacity only since they are GPU-accelerated; avoid width, height, top, left, margin, and padding.

How do I handle reduced-motion preferences?

Check prefers-reduced-motion and either disable animations or use simplified zero-duration configs, with a CSS fallback.

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.