| 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