xiaohongshu
xiaohongshu
gsap-animations
GSAP animation best practices for web design - scroll triggers, performance optimization, accessibility, responsive animations, and testing integration. Use when implementing or reviewing animations on WordPress or any web project.
Full skill instructions
Comprehensive guide for implementing professional, accessible, and performant animations using GSAP (GreenSock Animation Platform).
transform and opacity only (GPU-accelerated)width, height, top, left, margin, paddingwill-change sparinglyprefers-reduced-motion<!-- 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>
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');
// 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'
}
});
// 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%'
}
});
// Subtle parallax on images
gsap.to('.parallax-image', {
yPercent: -20,
ease: 'none',
scrollTrigger: {
trigger: '.parallax-section',
start: 'top bottom',
end: 'bottom top',
scrub: true
}
});
// 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%'
}
});
// 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%'
}
});
// 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');
// 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
});
/* 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 at start of script
document.documentElement.classList.add('js');
// 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
});
});
// Recalculate ScrollTrigger on resize
let resizeTimer;
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
ScrollTrigger.refresh();
}, 250);
});
// 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
});
// Use batch for many similar elements
ScrollTrigger.batch('.card', {
onEnter: batch => gsap.to(batch, {
opacity: 1,
y: 0,
stagger: 0.1
}),
start: 'top 85%'
});
// Cleanup when navigating (SPA) or component unmount
function cleanup() {
ScrollTrigger.getAll().forEach(st => st.kill());
gsap.killTweensOf('*');
}
// 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);
});
// 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!
});
// 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
});
// 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());
});
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);
}
// In Playwright test
await page.evaluate(() => {
if (window.completeAllAnimations) {
window.completeAllAnimations();
}
});
await page.waitForTimeout(500);
await page.screenshot({ path: 'screenshot.png', fullPage: true });
// 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());
<!-- 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>
ScrollTrigger.defaults({
markers: true // Shows start/end markers
});
gsap.to('.element', {
x: 100,
onStart: () => console.log('Animation started'),
onComplete: () => console.log('Animation completed'),
onUpdate: self => console.log('Progress:', self.progress())
});
// 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!');
markers: trueprefers-reduced-motion: reducexiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience