| name | elite-performance |
| description | Performance optimization for premium animated websites. Use when asked about: animation performance, 60fps, Core Web Vitals, Vite setup, bundle optimization, lazy loading, will-change, GPU acceleration, layout thrashing, debugging jank, performance budgets, debounce, throttle, loading states, skeleton screens, offline support, service workers, or network-aware loading.
|
Elite Performance
Maintain 60fps animations while hitting Core Web Vitals targets.
Quick Reference
Performance Budget (2026)
Core Web Vitals Targets
| Metric | Good | Needs Work | Poor |
|---|
| LCP (Largest Contentful Paint) | ≤ 2.5s | ≤ 4.0s | > 4.0s |
| INP (Interaction to Next Paint) | ≤ 200ms | ≤ 500ms | > 500ms |
| CLS (Cumulative Layout Shift) | ≤ 0.1 | ≤ 0.25 | > 0.25 |
Animation Targets
| Metric | Target |
|---|
| Frame rate | 60fps (16.67ms/frame) |
| Frame budget | < 10ms for JS/layout |
| Animation start | < 100ms response |
| Scroll jank | 0 dropped frames |
Bundle Targets
| Asset | Target |
|---|
| Initial JS | < 100KB (gzipped) |
| Initial CSS | < 50KB (gzipped) |
| GSAP core | ~25KB (gzipped) |
| Total initial | < 200KB (gzipped) |
GPU-Accelerated Properties
ONLY Animate These
transform: translateX() translateY() translateZ()
scale() rotate() skew();
opacity: 0 to 1;
filter: blur() brightness() contrast();
will-change: transform, opacity;
NEVER Animate These
width, height
top, right, bottom, left
margin, padding
font-size
border-width
background-color, color
border-color
box-shadow
text-shadow
Transform vs Position
.element {
animation: moveLeft 1s;
}
@keyframes moveLeft {
to { left: 100px; }
}
.element {
animation: moveLeft 1s;
}
@keyframes moveLeft {
to { transform: translateX(100px); }
}
Quick Performance Wins
1. Lazy Load Below-Fold Content
<img src="hero.jpg" alt="Hero" loading="eager">
<img src="feature.jpg" alt="Feature" loading="lazy">
<div class="lazy-section" data-component="heavy-animation">
</div>
2. Use content-visibility
.section {
content-visibility: auto;
contain-intrinsic-size: 0 500px;
}
3. Contain Expensive Effects
.animated-section {
contain: layout style paint;
}
.card {
contain: strict;
}
4. Reduce Motion When Appropriate
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
5. Optimize Scroll Handlers
window.addEventListener('scroll', handleScroll);
window.addEventListener('scroll', handleScroll, { passive: true });
gsap.to('.element', {
scrollTrigger: { }
});
GSAP Performance
Use gsap.context() for Cleanup
const ctx = gsap.context(() => {
gsap.to('.element', { x: 100 });
ScrollTrigger.create({ });
});
ctx.revert();
Batch ScrollTrigger Updates
ScrollTrigger.batch('.item', {
onEnter: batch => gsap.to(batch, {
opacity: 1,
y: 0,
stagger: 0.1
})
});
Use refreshPriority
ScrollTrigger.create({
trigger: '.section',
refreshPriority: -1
});
Lazy ScrollTriggers
const createTrigger = (element) => {
ScrollTrigger.create({
trigger: element,
start: 'top 80%',
onEnter: () => {
gsap.from(element, { opacity: 0, y: 50 });
},
once: true
});
};
gsap.utils.toArray('.section').forEach(createTrigger);
CSS Animation Performance
Efficient Keyframes
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideIn {
from {
opacity: 0;
margin-top: 30px;
}
to {
opacity: 1;
margin-top: 0;
}
}
will-change Best Practices
.card {
transition: transform 0.3s, opacity 0.3s;
}
.card:hover {
will-change: transform;
}
.card.animating {
will-change: transform, opacity;
}
Composite Layers
.animated-element {
transform: translateZ(0);
}
.animating {
will-change: transform;
}
Loading Strategy
Critical Path
<head>
<style></style>
<link rel="preload" href="hero.webp" as="image">
<link rel="preload" href="font.woff2" as="font" crossorigin>
<link rel="stylesheet" href="full.css" media="print" onload="this.media='all'">
</head>
<body>
<script src="gsap.min.js" defer></script>
<script src="app.js" defer></script>
</body>
Dynamic Imports
const loadScrollTrigger = async () => {
const { ScrollTrigger } = await import('gsap/ScrollTrigger');
gsap.registerPlugin(ScrollTrigger);
return ScrollTrigger;
};
const section = document.querySelector('.scroll-section');
const observer = new IntersectionObserver(async ([entry]) => {
if (entry.isIntersecting) {
await loadScrollTrigger();
initScrollAnimations();
observer.disconnect();
}
});
observer.observe(section);
Progressive Enhancement
const supportsAnimation = 'animate' in document.documentElement;
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (supportsAnimation && !prefersReducedMotion) {
import('./animations.js');
} else {
document.querySelectorAll('.animated').forEach(el => {
el.classList.add('animation-complete');
});
}
Memory Management
Clean Up Animations
const animations = [];
const scrollTriggers = [];
function initAnimations() {
animations.push(
gsap.to('.element', { x: 100 })
);
scrollTriggers.push(
ScrollTrigger.create({ trigger: '.section' })
);
}
function cleanup() {
animations.forEach(anim => anim.kill());
scrollTriggers.forEach(st => st.kill());
animations.length = 0;
scrollTriggers.length = 0;
}
const ctx = gsap.context(() => {
});
ctx.revert();
SplitText Cleanup
const splits = [];
function initTextAnimations() {
document.querySelectorAll('.split-text').forEach(el => {
const split = new SplitText(el, { type: 'chars' });
splits.push(split);
gsap.from(split.chars, {
opacity: 0,
y: 20,
stagger: 0.02
});
});
}
function cleanup() {
splits.forEach(split => split.revert());
splits.length = 0;
}
Event Listener Cleanup
const controller = new AbortController();
window.addEventListener('resize', handleResize, {
signal: controller.signal
});
window.addEventListener('scroll', handleScroll, {
passive: true,
signal: controller.signal
});
function cleanup() {
controller.abort();
}
Debugging Checklist
Performance Issues
-
Dropped frames?
- Check DevTools Performance panel
- Look for long tasks (> 50ms)
- Verify only compositor properties animated
-
Slow initial load?
- Check Network waterfall
- Verify critical path optimized
- Audit bundle sizes
-
Memory leaks?
- Check Memory panel over time
- Verify cleanup on navigation
- Watch for detached DOM nodes
-
Layout thrashing?
- Look for forced reflows in Performance
- Batch DOM reads/writes
- Use transform instead of position
Quick Checks
let lastTime = performance.now();
let frameCount = 0;
function measureFPS() {
frameCount++;
const now = performance.now();
if (now - lastTime >= 1000) {
console.log('FPS:', frameCount);
frameCount = 0;
lastTime = now;
}
requestAnimationFrame(measureFPS);
}
measureFPS();
const originalGetComputedStyle = window.getComputedStyle;
window.getComputedStyle = function(...args) {
console.trace('getComputedStyle called');
return originalGetComputedStyle.apply(this, args);
};
See debugging.md for comprehensive debugging techniques.
Resources