GSAP 实战案例

综合运用 GSAP 的各种功能,创建完整的动画交互效果。


🎯 案例 1:页面加载动画

完整页面加载序列

// 创建加载时间轴
const loadTl = gsap.timeline();
 
// 1. Logo 出现
loadTl.from(".logo", {
  scale: 0,
  rotation: -180,
  duration: 0.8,
  ease: "back.out(1.7)"
})
// 2. 导航栏出现
.from(".nav-item", {
  x: -30,
  opacity: 0,
  duration: 0.5,
  stagger: 0.1
}, "-=0.4")
// 3. 主标题出现
.from(".hero-title", {
  y: 50,
  opacity: 0,
  duration: 1,
  ease: "power3.out"
}, "-=0.3")
// 4. 副标题出现
.from(".hero-subtitle", {
  y: 30,
  opacity: 0,
  duration: 0.8
}, "-=0.5")
// 5. 按钮出现
.from(".cta-button", {
  scale: 0,
  opacity: 0,
  duration: 0.5,
  ease: "back.out(1.7)"
}, "-=0.3")
// 6. 背景元素
.from(".bg-element", {
  scale: 1.2,
  opacity: 0,
  duration: 1.5,
  ease: "power2.out"
}, 0);

🎯 案例 2:卡片悬停效果

3D 卡片翻转

const cards = document.querySelectorAll(".card");
 
cards.forEach(card => {
  const cardInner = card.querySelector(".card-inner");
  
  // 鼠标进入
  card.addEventListener("mouseenter", () => {
    gsap.to(card, {
      y: -10,
      scale: 1.05,
      duration: 0.3,
      ease: "power2.out"
    });
    
    gsap.to(cardInner, {
      rotationY: 5,
      rotationX: 5,
      duration: 0.3
    });
  });
  
  // 鼠标移动
  card.addEventListener("mousemove", (e) => {
    const rect = card.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;
    
    const centerX = rect.width / 2;
    const centerY = rect.height / 2;
    
    const rotateX = (y - centerY) / 10;
    const rotateY = (centerX - x) / 10;
    
    gsap.to(cardInner, {
      rotationX: rotateX,
      rotationY: rotateY,
      duration: 0.3
    });
  });
  
  // 鼠标离开
  card.addEventListener("mouseleave", () => {
    gsap.to(card, {
      y: 0,
      scale: 1,
      duration: 0.3,
      ease: "power2.in"
    });
    
    gsap.to(cardInner, {
      rotationX: 0,
      rotationY: 0,
      duration: 0.3
    });
  });
});

🎯 案例 3:滚动视差效果

多层视差滚动

// 背景层(慢速)
gsap.to(".parallax-bg", {
  y: -300,
  scrollTrigger: {
    trigger: ".parallax-container",
    start: "top top",
    end: "bottom top",
    scrub: true
  }
});
 
// 中间层(中速)
gsap.to(".parallax-mid", {
  y: -150,
  scrollTrigger: {
    trigger: ".parallax-container",
    start: "top top",
    end: "bottom top",
    scrub: true
  }
});
 
// 前景层(正常速度)
gsap.to(".parallax-front", {
  y: -50,
  scrollTrigger: {
    trigger: ".parallax-container",
    start: "top top",
    end: "bottom top",
    scrub: true
  }
});

🎯 案例 4:滚动进度指示器

页面滚动进度条

// 进度条
gsap.to(".progress-bar", {
  width: "100%",
  scrollTrigger: {
    trigger: "body",
    start: "top top",
    end: "bottom bottom",
    scrub: true
  }
});
 
// 百分比显示
gsap.to(".progress-text", {
  textContent: 100,
  scrollTrigger: {
    trigger: "body",
    start: "top top",
    end: "bottom bottom",
    scrub: true,
    onUpdate: (self) => {
      document.querySelector(".progress-text").textContent = 
        Math.round(self.progress * 100) + "%";
    }
  }
});

🎯 案例 5:固定导航栏

滚动时固定导航

ScrollTrigger.create({
  trigger: ".header",
  start: "top top",
  end: "bottom top",
  pin: true,
  pinSpacing: true
});
 
// 滚动时改变样式
gsap.to(".header", {
  backgroundColor: "rgba(255, 255, 255, 0.95)",
  backdropFilter: "blur(10px)",
  scrollTrigger: {
    trigger: "body",
    start: "top -100",
    end: "bottom top",
    scrub: true
  }
});

🎯 案例 6:数字计数动画

滚动触发数字计数

const counters = document.querySelectorAll(".counter");
 
counters.forEach(counter => {
  const target = parseInt(counter.dataset.target);
  
  gsap.to(counter, {
    textContent: target,
    duration: 2,
    snap: { textContent: 1 },
    ease: "power2.out",
    scrollTrigger: {
      trigger: counter,
      start: "top 80%",
      toggleActions: "play none none none"
    }
  });
});

🎯 案例 7:模态框动画

完整的模态框动画序列

function showModal() {
  const modal = document.querySelector(".modal");
  const backdrop = document.querySelector(".modal-backdrop");
  
  // 显示遮罩
  gsap.set(backdrop, { display: "block" });
  gsap.fromTo(backdrop,
    { opacity: 0 },
    { opacity: 1, duration: 0.3 }
  );
  
  // 显示模态框
  gsap.set(modal, { display: "block" });
  gsap.fromTo(modal,
    {
      opacity: 0,
      scale: 0.8,
      y: 50
    },
    {
      opacity: 1,
      scale: 1,
      y: 0,
      duration: 0.4,
      ease: "back.out(1.7)"
    }
  );
  
  // 内容依次出现
  const tl = gsap.timeline({ delay: 0.2 });
  tl.from(".modal-header", { y: -20, opacity: 0, duration: 0.3 })
    .from(".modal-body", { y: 20, opacity: 0, duration: 0.3 }, "-=0.2")
    .from(".modal-footer", { y: 20, opacity: 0, duration: 0.3 }, "-=0.2");
}
 
function hideModal() {
  const modal = document.querySelector(".modal");
  const backdrop = document.querySelector(".modal-backdrop");
  
  const tl = gsap.timeline({
    onComplete: () => {
      gsap.set([modal, backdrop], { display: "none" });
    }
  });
  
  tl.to(modal, {
    opacity: 0,
    scale: 0.8,
    y: 50,
    duration: 0.3,
    ease: "back.in(1.7)"
  })
  .to(backdrop, {
    opacity: 0,
    duration: 0.3
  }, 0);
}

🎯 案例 8:文字拆分动画

标题文字依次出现

function splitTextToChars(element) {
  const text = element.textContent;
  const chars = text.split("").map(char => {
    const span = document.createElement("span");
    span.textContent = char === " " ? "\u00A0" : char;
    span.style.display = "inline-block";
    return span;
  });
  
  element.textContent = "";
  chars.forEach(char => element.appendChild(char));
  
  return Array.from(element.children);
}
 
// 使用
const title = document.querySelector(".title");
const chars = splitTextToChars(title);
 
gsap.from(chars, {
  opacity: 0,
  y: 50,
  rotationX: -90,
  duration: 0.5,
  stagger: 0.03,
  ease: "back.out(1.7)",
  scrollTrigger: {
    trigger: title,
    start: "top 80%",
    toggleActions: "play none none reverse"
  }
});

🎯 案例 9:图片画廊动画

图片依次出现

const images = gsap.utils.toArray(".gallery-item");
 
images.forEach((image, i) => {
  gsap.from(image, {
    scale: 0.8,
    opacity: 0,
    duration: 0.6,
    ease: "power2.out",
    scrollTrigger: {
      trigger: image,
      start: "top 85%",
      toggleActions: "play none none reverse"
    },
    delay: i * 0.1
  });
});

🎯 案例 10:按钮交互效果

按钮点击波纹效果

const buttons = document.querySelectorAll(".button");
 
buttons.forEach(button => {
  button.addEventListener("click", function(e) {
    // 创建波纹元素
    const ripple = document.createElement("span");
    ripple.classList.add("ripple");
    this.appendChild(ripple);
    
    // 获取点击位置
    const rect = this.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;
    
    // 设置位置
    gsap.set(ripple, {
      left: x,
      top: y,
      xPercent: -50,
      yPercent: -50
    });
    
    // 动画
    gsap.to(ripple, {
      scale: 4,
      opacity: 0,
      duration: 0.6,
      ease: "power2.out",
      onComplete: () => ripple.remove()
    });
    
    // 按钮缩放
    gsap.to(button, {
      scale: 0.95,
      duration: 0.1,
      yoyo: true,
      repeat: 1,
      ease: "power2.inOut"
    });
  });
});

🎯 案例 11:页面切换动画

页面过渡效果

function pageTransition(outElement, inElement) {
  const tl = gsap.timeline();
  
  // 退出动画
  tl.to(outElement, {
    opacity: 0,
    y: -50,
    duration: 0.5,
    ease: "power2.in"
  })
  // 进入动画
  .fromTo(inElement,
    {
      opacity: 0,
      y: 50
    },
    {
      opacity: 1,
      y: 0,
      duration: 0.5,
      ease: "power2.out"
    },
    "-=0.3"
  );
}

🎯 案例 12:加载动画

旋转加载器

// 创建加载动画
const loader = gsap.timeline({ repeat: -1 });
 
loader.to(".loader-circle", {
  rotation: 360,
  duration: 1,
  ease: "none"
});
 
// 加载完成后
function hideLoader() {
  gsap.to(".loader", {
    opacity: 0,
    scale: 0,
    duration: 0.3,
    onComplete: () => {
      document.querySelector(".loader").style.display = "none";
      loader.kill();
    }
  });
}

💡 最佳实践总结

1. 性能优化

// ✅ 使用 transform 和 opacity
gsap.to(".box", {
  x: 100,      // transform
  opacity: 0.5 // opacity
});
 
// ❌ 避免触发重排
gsap.to(".box", {
  left: "100px",  // 触发重排
  width: "200px"  // 触发重排
});

2. 合理使用 Timeline

// ✅ 推荐:使用 Timeline 管理多个动画
const tl = gsap.timeline();
tl.to(".box1", { x: 100 })
  .to(".box2", { y: 100 }, "-=0.5");

3. 及时清理

// 保存动画引用
const animation = gsap.to(".box", { x: 100 });
 
// 组件卸载时清理
function cleanup() {
  animation.kill();
  ScrollTrigger.getAll().forEach(st => st.kill());
}

4. 响应式处理

// 窗口大小改变时刷新
window.addEventListener("resize", () => {
  ScrollTrigger.refresh();
});

🔗 相关资源


GSAP 实战案例 动画效果 交互设计