Animaciones gsap
Pre-production audit protocol for static websites — 10 sequential skills covering performance, accessibility, SEO, security, and more
npx -y skills add magallon/website-audit-toolkit --skill animaciones-gsapAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Patrones de animación GSAP + ScrollTrigger en Vanilla JS para landing pages. Usa este skill cuando el usuario pida animaciones, transiciones, efectos al scroll, o interacciones avanzadas en una landing page. Incluye navbar con cambio de estado, fade-up, split text, parallax, typewriter, acordeón, baraja de cards y más.
SKILL.md
11.8 KB, as published. Nobody here has run it
Skill: Animaciones GSAP para Landing Pages (Vanilla JS)
Reglas Generales de Animación
- Siempre carga GSAP y ScrollTrigger vía CDN al final del body con
defer. - Todo el código de animación va en
app.js. Nunca en archivos separados. - Usa
gsap.context()para agrupar animaciones y facilitar cleanup. - Registra ScrollTrigger una sola vez al inicio:
gsap.registerPlugin(ScrollTrigger); - Todas las animaciones deben ser sobrias. Duración mínima 0.4s. Ease recomendado:
power2.outopower3.out. - Nunca uses animaciones "infantiles" (bounce exagerado, colores parpadeantes, zoom agresivo).
- Respeta
prefers-reduced-motion: desactiva animaciones si el usuario lo pide.
Estructura base de app.js
document.addEventListener('DOMContentLoaded', () => {
gsap.registerPlugin(ScrollTrigger);
const ctx = gsap.context(() => {
// Aquí van todas las animaciones
initNavbar();
initHeroAnimations();
initScrollAnimations();
initInteractions();
});
// Cleanup si se necesita
// ctx.revert();
});
Verificar prefers-reduced-motion
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (!prefersReducedMotion) {
// Inicializar animaciones
}
Patrón 1: Navbar con cambio de estado al scroll
Navbar tipo píldora flotante que cambia de transparente a sólida al hacer scroll.
function initNavbar() {
const navbar = document.querySelector('.navbar');
ScrollTrigger.create({
start: 'top -80',
onUpdate: (self) => {
if (self.direction === 1 && self.scroll() > 80) {
navbar.classList.add('navbar-scrolled');
}
if (self.scroll() < 80) {
navbar.classList.remove('navbar-scrolled');
}
}
});
}
CSS necesario en styles.css:
.navbar {
position: fixed;
top: 1rem;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
padding: 0.75rem 2rem;
border-radius: 9999px;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
background: transparent;
}
.navbar-scrolled {
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.08);
border: 1px solid rgba(255, 255, 255, 0.3);
}
Patrón 2: Fade-up escalonado (Hero)
Aparición escalonada de elementos del hero de abajo hacia arriba.
function initHeroAnimations() {
const heroElements = gsap.utils.toArray('.hero-animate');
gsap.from(heroElements, {
y: 60,
opacity: 0,
duration: 0.8,
stagger: 0.15,
ease: 'power3.out',
delay: 0.3
});
}
HTML: añadir clase hero-animate a cada elemento que debe aparecer en secuencia.
<h1 class="hero-animate">Titular principal</h1>
<p class="hero-animate">Subtítulo descriptivo</p>
<div class="hero-animate">
<a href="#" class="btn-cta">CTA Principal</a>
<a href="#" class="btn-secondary">CTA Secundario</a>
</div>
Patrón 3: Aparición al scroll (Secciones generales)
Elementos que aparecen suavemente al entrar en el viewport.
function initScrollAnimations() {
// Fade up general
gsap.utils.toArray('.animate-on-scroll').forEach(el => {
gsap.from(el, {
y: 40,
opacity: 0,
duration: 0.7,
ease: 'power2.out',
scrollTrigger: {
trigger: el,
start: 'top 85%',
toggleActions: 'play none none none'
}
});
});
// Entrada desde izquierda
gsap.utils.toArray('.animate-from-left').forEach(el => {
gsap.from(el, {
x: -60,
opacity: 0,
duration: 0.7,
ease: 'power2.out',
scrollTrigger: {
trigger: el,
start: 'top 85%',
toggleActions: 'play none none none'
}
});
});
// Entrada desde derecha
gsap.utils.toArray('.animate-from-right').forEach(el => {
gsap.from(el, {
x: 60,
opacity: 0,
duration: 0.7,
ease: 'power2.out',
scrollTrigger: {
trigger: el,
start: 'top 85%',
toggleActions: 'play none none none'
}
});
});
// Escalonado para grupos (cards, features, timeline steps)
gsap.utils.toArray('.animate-stagger-group').forEach(group => {
const items = group.querySelectorAll('.stagger-item');
gsap.from(items, {
y: 40,
opacity: 0,
duration: 0.6,
stagger: 0.12,
ease: 'power2.out',
scrollTrigger: {
trigger: group,
start: 'top 80%',
toggleActions: 'play none none none'
}
});
});
}
Patrón 4: Split text (Texto que aparece por palabras/líneas)
Para secciones de manifiesto o titulares dramáticos.
function initSplitText(selector) {
const elements = document.querySelectorAll(selector);
elements.forEach(el => {
const text = el.textContent;
const words = text.split(' ');
el.innerHTML = words.map(word =>
`<span class="inline-block overflow-hidden"><span class="split-word inline-block">${word}</span></span>`
).join(' ');
gsap.from(el.querySelectorAll('.split-word'), {
y: '100%',
opacity: 0,
duration: 0.6,
stagger: 0.05,
ease: 'power3.out',
scrollTrigger: {
trigger: el,
start: 'top 80%',
toggleActions: 'play none none none'
}
});
});
}
Patrón 5: Typewriter (Texto que se escribe solo)
Efecto de terminal/telemetría con cursor intermitente.
function initTypewriter(selector, messages, speed = 50) {
const el = document.querySelector(selector);
let messageIndex = 0;
let charIndex = 0;
let isDeleting = false;
function type() {
const currentMessage = messages[messageIndex];
if (isDeleting) {
el.textContent = currentMessage.substring(0, charIndex - 1);
charIndex--;
} else {
el.textContent = currentMessage.substring(0, charIndex + 1);
charIndex++;
}
let delay = isDeleting ? speed / 2 : speed;
if (!isDeleting && charIndex === currentMessage.length) {
delay = 2000; // Pausa al completar
isDeleting = true;
} else if (isDeleting && charIndex === 0) {
isDeleting = false;
messageIndex = (messageIndex + 1) % messages.length;
delay = 500;
}
setTimeout(type, delay);
}
type();
}
// Uso:
// initTypewriter('.typewriter-text', [
// 'Optimizando tu embudo…',
// 'Generando landing…',
// 'Ajustando conversiones…'
// ]);
CSS para el cursor:
.typewriter-container::after {
content: '|';
animation: blink 0.8s infinite;
color: var(--color-accent);
font-weight: 300;
}
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
Patrón 6: Acordeón (FAQ)
Acordeón con transición suave de altura.
function initAccordion() {
const items = document.querySelectorAll('.accordion-item');
items.forEach(item => {
const trigger = item.querySelector('.accordion-trigger');
const content = item.querySelector('.accordion-content');
const icon = item.querySelector('.accordion-icon');
// Iniciar cerrado
gsap.set(content, { height: 0, overflow: 'hidden' });
trigger.addEventListener('click', () => {
const isOpen = item.classList.contains('is-open');
// Cerrar todos los demás
items.forEach(other => {
if (other !== item && other.classList.contains('is-open')) {
other.classList.remove('is-open');
gsap.to(other.querySelector('.accordion-content'), {
height: 0,
duration: 0.4,
ease: 'power2.inOut'
});
gsap.to(other.querySelector('.accordion-icon'), {
rotation: 0,
duration: 0.3
});
}
});
// Toggle actual
if (isOpen) {
item.classList.remove('is-open');
gsap.to(content, { height: 0, duration: 0.4, ease: 'power2.inOut' });
gsap.to(icon, { rotation: 0, duration: 0.3 });
} else {
item.classList.add('is-open');
gsap.to(content, { height: 'auto', duration: 0.4, ease: 'power2.inOut' });
gsap.to(icon, { rotation: 45, duration: 0.3 });
}
});
});
}
Patrón 7: Parallax sutil en fondos
Movimiento sutil del fondo al hacer scroll para dar profundidad.
function initParallax() {
gsap.utils.toArray('.parallax-bg').forEach(bg => {
gsap.to(bg, {
yPercent: -20,
ease: 'none',
scrollTrigger: {
trigger: bg.parentElement,
start: 'top bottom',
end: 'bottom top',
scrub: 1
}
});
});
}
HTML necesario:
<section class="relative overflow-hidden">
<div class="parallax-bg absolute inset-0 -top-[20%] -bottom-[20%]">
<img src="imagen.jpg" class="w-full h-full object-cover" alt="Descripción">
</div>
<div class="relative z-10">
<!-- Contenido de la sección -->
</div>
</section>
Patrón 8: Counter animado (Números que suben)
Para secciones de métricas o prueba social.
function initCounters() {
gsap.utils.toArray('.counter').forEach(counter => {
const target = parseInt(counter.getAttribute('data-target'));
const suffix = counter.getAttribute('data-suffix') || '';
gsap.from(counter, {
textContent: 0,
duration: 2,
ease: 'power1.out',
snap: { textContent: 1 },
scrollTrigger: {
trigger: counter,
start: 'top 85%',
toggleActions: 'play none none none'
},
onUpdate: function() {
counter.textContent = Math.ceil(parseFloat(counter.textContent)) + suffix;
}
});
});
}
HTML:
<span class="counter" data-target="147" data-suffix="+">0</span>
Patrón 9: Menú hamburger móvil
Toggle de menú en móvil con animación.
function initMobileMenu() {
const menuBtn = document.querySelector('.menu-toggle');
const mobileMenu = document.querySelector('.mobile-menu');
const menuLinks = mobileMenu.querySelectorAll('a');
let isOpen = false;
menuBtn.addEventListener('click', () => {
isOpen = !isOpen;
menuBtn.classList.toggle('is-active');
if (isOpen) {
gsap.to(mobileMenu, {
clipPath: 'circle(150% at top right)',
duration: 0.5,
ease: 'power3.inOut'
});
gsap.from(menuLinks, {
y: 20,
opacity: 0,
stagger: 0.08,
duration: 0.4,
delay: 0.2
});
} else {
gsap.to(mobileMenu, {
clipPath: 'circle(0% at top right)',
duration: 0.4,
ease: 'power2.in'
});
}
});
// Cerrar al hacer click en un link
menuLinks.forEach(link => {
link.addEventListener('click', () => {
isOpen = false;
menuBtn.classList.remove('is-active');
gsap.to(mobileMenu, {
clipPath: 'circle(0% at top right)',
duration: 0.4,
ease: 'power2.in'
});
});
});
}
CSS inicial del menú:
.mobile-menu {
clip-path: circle(0% at top right);
position: fixed;
inset: 0;
z-index: 999;
}
Patrón 10: Punto "en vivo" pulsante
Indicador de estado tipo "sistema activo".
.pulse-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: #22c55e;
position: relative;
display: inline-block;
}
.pulse-dot::before {
content: '';
position: absolute;
inset: -3px;
border-radius: 50%;
background-color: #22c55e;
animation: pulse-ring 2s ease-out infinite;
}
@keyframes pulse-ring {
0% { transform: scale(1); opacity: 0.5; }
100% { transform: scale(2.5); opacity: 0; }
}
HTML:
<span class="pulse-dot"></span> Sistema activo