返回 Skill 列表
extension
分类: 开发与工程无需 API Key

web-animation

使用GSAP ScrollTrigger、Lenis平滑滚动、视差效果和电影级页面过渡来构建沉浸式的滚动驱动网站。适用于需要超越静态设计的动态效果和精致感的高端企业网站、登录页或营销微型网站。

person作者: jakexiaohubgithub

Web Animation

Build websites with scroll-driven animations, parallax, smooth scrolling, and page transitions — the kind of motion that makes a site feel premium (like mont-fort.com, linear.app, stripe.com).

When to Use This Skill

  • Building corporate/luxury/editorial websites that need motion
  • Adding scroll-triggered animations to existing pages
  • Creating parallax sections, sticky reveals, or cinematic scroll experiences
  • Implementing smooth scrolling with Lenis
  • Adding page transitions (Astro View Transitions, Barba.js)
  • When /design produces good static design but needs animation layer

Tech Stack Decision

Static site with animations?
  Astro + GSAP + Lenis + View Transitions (recommended)

React/Next.js app?
  Next.js + Framer Motion + Lenis + GSAP ScrollTrigger

Vanilla HTML?
  GSAP + ScrollTrigger + Lenis + Barba.js

Core Libraries

1. Lenis — Smooth Scroll (always install)

npm install lenis
// src/lib/lenis.ts
import Lenis from 'lenis'

const lenis = new Lenis({
  duration: 1.2,
  easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
  orientation: 'vertical',
  smoothWheel: true,
})

function raf(time: number) {
  lenis.raf(time)
  requestAnimationFrame(raf)
}
requestAnimationFrame(raf)

export default lenis
/* Required CSS */
@import 'lenis/css';

html.lenis, html.lenis body {
  height: auto;
}
.lenis.lenis-smooth {
  scroll-behavior: auto !important;
}

2. GSAP + ScrollTrigger — Scroll Animations

npm install gsap
import gsap from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'

gsap.registerPlugin(ScrollTrigger)

// Sync Lenis with ScrollTrigger
import lenis from './lenis'
lenis.on('scroll', ScrollTrigger.update)
gsap.ticker.add((time) => lenis.raf(time * 1000))
gsap.ticker.lagSmoothing(0)

3. Framer Motion (React/Next.js only)

npm install motion
import { motion, useScroll, useTransform } from 'motion/react'

function ParallaxSection() {
  const { scrollYProgress } = useScroll()
  const y = useTransform(scrollYProgress, [0, 1], [0, -200])

  return <motion.div style={{ y }}>Content</motion.div>
}

Animation Patterns

Pattern 1: Fade + Slide on Scroll (most common)

// Reveal elements as they enter viewport
gsap.utils.toArray('.reveal').forEach((el) => {
  gsap.from(el as Element, {
    y: 60,
    opacity: 0,
    duration: 1,
    ease: 'power3.out',
    scrollTrigger: {
      trigger: el as Element,
      start: 'top 85%',
      toggleActions: 'play none none none',
    },
  })
})
<section class="reveal">
  <h2>Section Title</h2>
  <p>Content fades in as you scroll</p>
</section>

Pattern 2: Staggered Children

gsap.utils.toArray('.stagger-group').forEach((group) => {
  const children = (group as Element).querySelectorAll('.stagger-item')
  gsap.from(children, {
    y: 40,
    opacity: 0,
    duration: 0.8,
    stagger: 0.15,
    ease: 'power2.out',
    scrollTrigger: {
      trigger: group as Element,
      start: 'top 80%',
    },
  })
})

Pattern 3: Parallax Image

gsap.utils.toArray('.parallax-img').forEach((img) => {
  gsap.to(img as Element, {
    yPercent: -20,
    ease: 'none',
    scrollTrigger: {
      trigger: img as Element,
      start: 'top bottom',
      end: 'bottom top',
      scrub: true,
    },
  })
})
.parallax-container {
  overflow: hidden;
  position: relative;
}
.parallax-img {
  width: 100%;
  height: 120%; /* Extra height for parallax travel */
  object-fit: cover;
}

Pattern 4: Sticky Section with Progress

const section = document.querySelector('.sticky-section')
const content = section?.querySelector('.sticky-content')

ScrollTrigger.create({
  trigger: section,
  start: 'top top',
  end: '+=200%',
  pin: true,
  scrub: 1,
  onUpdate: (self) => {
    // Use self.progress (0-1) to drive animations
    gsap.to(content, { opacity: self.progress, y: -50 * self.progress })
  },
})

Pattern 5: Horizontal Scroll Section

const container = document.querySelector('.horizontal-scroll')
const panels = gsap.utils.toArray('.panel')

gsap.to(panels, {
  xPercent: -100 * (panels.length - 1),
  ease: 'none',
  scrollTrigger: {
    trigger: container,
    pin: true,
    scrub: 1,
    snap: 1 / (panels.length - 1),
    end: () => '+=' + (container as HTMLElement).offsetWidth,
  },
})

Pattern 6: Text Split + Reveal

Use a safe DOM approach for splitting text into animatable elements:

function splitText(element: Element) {
  const text = element.textContent || ''
  const words = text.split(' ')

  // Clear and rebuild with spans using DOM API
  element.textContent = ''
  words.forEach((word, i) => {
    const outer = document.createElement('span')
    outer.className = 'word'
    outer.style.display = 'inline-block'
    outer.style.overflow = 'hidden'

    const inner = document.createElement('span')
    inner.className = 'word-inner'
    inner.style.display = 'inline-block'
    inner.textContent = word

    outer.appendChild(inner)
    element.appendChild(outer)

    if (i < words.length - 1) {
      element.appendChild(document.createTextNode(' '))
    }
  })

  return element.querySelectorAll('.word-inner')
}

gsap.utils.toArray('.text-reveal').forEach((el) => {
  const words = splitText(el as Element)
  gsap.from(words, {
    yPercent: 100,
    duration: 0.8,
    stagger: 0.05,
    ease: 'power3.out',
    scrollTrigger: {
      trigger: el as Element,
      start: 'top 85%',
    },
  })
})

Pattern 7: Image Clip Reveal

gsap.utils.toArray('.clip-reveal').forEach((el) => {
  gsap.from(el as Element, {
    clipPath: 'inset(100% 0 0 0)',
    duration: 1.2,
    ease: 'power4.inOut',
    scrollTrigger: {
      trigger: el as Element,
      start: 'top 80%',
    },
  })
})
.clip-reveal {
  clip-path: inset(0 0 0 0);
  will-change: clip-path;
}

Pattern 8: Counter / Number Animation

gsap.utils.toArray('.counter').forEach((el) => {
  const target = parseInt((el as Element).getAttribute('data-target') || '0')
  gsap.to(el, {
    textContent: target,
    duration: 2,
    ease: 'power2.out',
    snap: { textContent: 1 },
    scrollTrigger: {
      trigger: el as Element,
      start: 'top 85%',
    },
  })
})

Pattern 9: Page Load Sequence

// Run once on page load — orchestrated entrance
const tl = gsap.timeline({ defaults: { ease: 'power3.out' } })

tl.from('.hero-title', { y: 80, opacity: 0, duration: 1 })
  .from('.hero-subtitle', { y: 40, opacity: 0, duration: 0.8 }, '-=0.5')
  .from('.hero-cta', { y: 30, opacity: 0, duration: 0.6 }, '-=0.4')
  .from('.hero-image', {
    scale: 1.1,
    opacity: 0,
    duration: 1.2,
    clipPath: 'inset(10% 10% 10% 10%)',
  }, '-=0.8')
  .from('.nav', { y: -20, opacity: 0, duration: 0.5 }, '-=0.6')

Page Transitions

Astro View Transitions (recommended for Astro)

---
// src/layouts/Layout.astro
import { ViewTransitions } from 'astro:transitions'
---
<html>
  <head>
    <ViewTransitions />
  </head>
  <body>
    <slot />
  </body>
</html>
<!-- Named transitions for specific elements -->
<h1 transition:name="page-title" transition:animate="slide">
  {title}
</h1>
<img transition:name="hero-image" transition:animate="fade" src={image} />

Barba.js (vanilla/multi-page)

npm install @barba/core
import barba from '@barba/core'

barba.init({
  transitions: [{
    name: 'fade',
    leave(data) {
      return gsap.to(data.current.container, {
        opacity: 0,
        duration: 0.5,
      })
    },
    enter(data) {
      return gsap.from(data.next.container, {
        opacity: 0,
        duration: 0.5,
      })
    },
  }],
})

Accessibility

ALWAYS respect motion preferences:

const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)')

if (prefersReducedMotion.matches) {
  // Disable smooth scroll
  lenis.destroy()
  // Kill all ScrollTrigger animations
  ScrollTrigger.getAll().forEach((st) => st.kill())
  // Set all animated elements to final state
  gsap.set('.reveal, .stagger-item, .text-reveal', {
    opacity: 1,
    y: 0,
    clipPath: 'none',
  })
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

Performance Rules

  1. Use will-change sparingly — only on elements about to animate
  2. Animate transforms and opacity only — never animate width, height, top, left
  3. Use scrub: true for scroll-linked (not scrub: 0.1 unless you need lag)
  4. Lazy load images below the fold
  5. Use gsap.set() for initial states instead of CSS (avoids FOUC)
  6. Kill ScrollTriggers on cleanup (SPA route changes)
// Cleanup pattern for React/Next.js
useEffect(() => {
  const ctx = gsap.context(() => {
    // All animations here
  }, containerRef)

  return () => ctx.revert() // Kills all animations + ScrollTriggers
}, [])

Section Blueprint: Corporate Page (mont-fort.com style)

1. HERO
   - Full viewport height
   - Large serif heading with text-reveal animation
   - Subtle parallax background image
   - Scroll indicator at bottom (opacity pulse)
   - Page load sequence: title -> subtitle -> CTA -> image

2. INTRO / ADVANTAGE
   - Centered text block with generous padding
   - Fade + slide reveal on scroll
   - Supporting image with clip-reveal

3. FEATURES / SERVICES
   - Staggered card grid or alternating image-text rows
   - Each card fades in with stagger delay
   - Icons or images with parallax offset

4. LEADERSHIP / TEAM
   - Grid with hover effects (scale + overlay)
   - Staggered entrance

5. STATS / NUMBERS
   - Counter animation on scroll
   - Large display numbers

6. CTA / CONTACT
   - Sticky section or full-viewport
   - Background color transition

7. FOOTER
   - Theme-aware (dark/light based on scroll context)
   - Minimal, multi-column layout

External Skills to Install

For maximum animation capability, install these alongside this skill:

# Option A: Full animation skillstack (22 skills)
claude plugin marketplace add freshtechbro/claudedesignskills

# Option B: Individual skills
# Lenis smooth scroll
npx -y @lobehub/market-cli skills install orkait-ai-skill-agent-lenis-react

# Scroll Experience Architect
npx -y @lobehub/market-cli skills install rootcastleco-rei-skills-scroll-experience

Anti-Patterns

| Do NOT | Do Instead | |--------|-----------| | Animate everything on scroll | Pick 3-5 key moments per page | | Use bounce or elastic easing on corporate sites | Use power3.out or power4.inOut | | Apply parallax to text (readability) | Apply parallax to images/backgrounds only | | Fade in every paragraph | Fade in section headings and key visuals | | Use animation delays > 0.3s between items | Keep stagger tight: 0.1-0.2s | | Ignore mobile (touch scrolling is different) | Test on real devices, reduce motion on mobile | | Stack multiple animation libraries | Pick one primary: GSAP OR Framer Motion |

Integration with /design

This skill extends the /design command. Use them together:

/design corporate landing page for energy company    # Static design
# Then add animation layer using this skill's patterns

Or request animation directly:

/design animated corporate site like mont-fort.com with scroll reveals and parallax