关于 awwwards-animations
Awwwards Animations 是面向开发者的 React 优先型网页动画技能,帮助制作动画网页体验。它按效果与视觉任务推荐库,并提供实现模式、性能建议和无障碍检查。
主要功能
- 01按动画任务选择库
- 02动画工具的 React 模式
- 03滚动效果与页面过渡指引
- 04SVG、Canvas 与 3D 技法
- 05性能与无障碍检查
适用场景
- 01为网站添加滚动驱动效果
- 02制作页面过渡与文字显现动画
- 03创建生成式、几何或 3D 视觉效果
- 04检查动画性能与减少动态效果支持
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ devmartinese/ awwwards-animations-skill --skill awwwards-animations安装后在 Claude Code 中输入/awwwards-animations确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
.gitignoreREADME.mdreferences/advanced-patterns.mddesign-philosophy.mdanimejs-react.mdaudio-reactive.mdgeometric-shapes.mdalgorithmic-art.mdgeometric-puzzles.mdperformance.mdgsap-react.mdmotion-patterns.mdlenis-react.mdphysics-2d.mdtext-effects.md详细介绍
Awwwards 动画
打造达到 Awwwards/FWA 品质水准的高级网页动画。React 优先的方法。60 fps 不容妥协。
决策矩阵
| 任务 | 库 | 原因 |
|---|---|---|
| 滚动驱动的动画 | GSAP + ScrollTrigger + useGSAP | 行业标准,控制能力最佳 |
| 平滑滚动 | Lenis + ReactLenis | 性能最佳,可与 ScrollTrigger 配合使用 |
| React 原生动画 | Motion (Framer Motion) | 原生 React,使用 useScroll/useTransform |
| 简单/轻量效果 | Anime.js 4.0 | 体积小,API 简洁 |
| 复杂时间轴 | GSAP | 时间轴控制能力无可匹敌 |
| SVG 变形 | GSAP MorphSVG 或 Anime.js | 两者都很出色 |
| 3D + 动画 | Three.js + GSAP | GSAP 控制 Three.js 对象 |
| 页面过渡 | AnimatePresence 或 GSAP | Motion 适用于 React,GSAP 适用于复杂场景 |
| 几何形状(矢量) | SVG + GSAP/Motion | 原生支持,可动画化 |
| 几何形状(画布) | Canvas 2D API | 可编程,性能出色 |
| 伪 3D 形状 | Zdog | 扁平风格 3D,约 2 kb |
| 创意编程/生成式创作 | p5.js | 生态系统丰富 |
| 音频响应 | Tone.js | Web Audio、合成器、效果 |
| 2D 物理 | Matter.js | 重力、碰撞、约束 |
| 算法/生成艺术 | Canvas 2D + p5.js | 由数学驱动的视觉效果 |
| 分形/L 系统 | Canvas 2D 递归绘制 | 递归渲染 |
| 镶嵌/几何谜题 | SVG + GSAP | 精确的动画变换 |
| 高级动态排版 | GSAP SplitText + Canvas | 可逐字符控制 |
| 故障效果 | CSS + GSAP | 分层 RGB 分离、clip-path |
| 粗野主义动画 | 原生 CSS + Motion | 硬切换,无缓动 |
| 极简主义动画 | Motion 弹簧 | 微妙、恰到好处的动态效果 |
安装(最新稳定版 - 2025)
# GSAP + React hook (v3.14.1)
npm install gsap @gsap/react
# Lenis (v1.3.17) - includes React components
npm install lenis
# Motion (Framer Motion)
npm install motion
# Anime.js (v4.0.0)
npm install animejs
React 设置
1. GSAP 配置(全应用)
// lib/gsap.ts
'use client' // Next.js App Router
import gsap from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
import { useGSAP } from '@gsap/react'
// Register plugins once
gsap.registerPlugin(ScrollTrigger, useGSAP)
export { gsap, ScrollTrigger, useGSAP }
2. Lenis + GSAP ScrollTrigger 集成(关键)
// components/SmoothScroll.tsx
'use client'
import { ReactLenis, useLenis } from 'lenis/react'
import { useEffect } from 'react'
import { gsap, ScrollTrigger } from '@/lib/gsap'
export function SmoothScroll({ children }: { children: React.ReactNode }) {
const lenis = useLenis()
useEffect(() => {
if (!lenis) return
lenis.on('scroll', ScrollTrigger.update)
gsap.ticker.add((time) => lenis.raf(time * 1000))
gsap.ticker.lagSmoothing(0)
return () => { gsap.ticker.remove(lenis?.raf) }
}, [lenis])
return (
<ReactLenis root options={{ lerp: 0.1, duration: 1.2, smoothWheel: true }}>
{children}
</ReactLenis>
)
}
// Wrap in layout: <SmoothScroll>{children}</SmoothScroll>
核心模式(React)
详细实现请参阅以下参考文档:
- GSAP + useGSAP:参阅 references/gsap-react.md
- Motion (Framer Motion):参阅 references/motion-patterns.md
- Anime.js 4.0:参阅 references/animejs-react.md
- Lenis React:参阅 references/lenis-react.md
- 几何形状:参阅 references/geometric-shapes.md(SVG、Canvas、Zdog、p5.js、俄罗斯方块风格)
- 音频响应:参阅 references/audio-reactive.md(Tone.js、Web Audio、滚动音频)
- 2D 物理:参阅 references/physics-2d.md(Matter.js、碰撞、约束)
- 高级内容(Three.js、WebGL):参阅 references/advanced-patterns.md
- 算法与生成艺术:参阅 references/algorithmic-art.md(分形、L 系统、流场、吸引子、噪声、神圣几何)
- 高级文本效果:参阅 references/text-effects.md(故障效果、动态排版、变形、爆炸、环形文字、文字扰动)
- 几何谜题:参阅 references/geometric-puzzles.md(Dudeney、七巧板、镶嵌、彭罗斯、骨牌多格拼块)
- 设计理念:参阅 references/design-philosophy.md(粗野主义、极简主义、抽象风格、风格混搭、配色方案)
- 性能:参阅 references/performance.md
快速模式(React)
1. 磁性光标(GSAP + useGSAP)
'use client'
import { useRef, useEffect } from 'react'
import { gsap, useGSAP } from '@/lib/gsap'
export function MagneticCursor() {
const cursorRef = useRef<HTMLDivElement>(null)
const pos = useRef({ x: 0, y: 0, cx: 0, cy: 0 })
useEffect(() => {
const h = (e: MouseEvent) => { pos.current.x = e.clientX; pos.current.y = e.clientY }
window.addEventListener('mousemove', h)
return () => window.removeEventListener('mousemove', h)
}, [])
useGSAP(() => {
gsap.ticker.add(() => {
const p = pos.current
p.cx += (p.x - p.cx) * 0.15; p.cy += (p.y - p.cy) * 0.15
gsap.set(cursorRef.current, { x: p.cx, y: p.cy })
})
})
return <div ref={cursorRef} className="fixed w-10 h-10 border border-white rounded-full pointer-events-none mix-blend-difference z-[9999] -translate-x-1/2 -translate-y-1/2" />
}
2. 磁性按钮(Motion)
'use client'
import { useRef, useState } from 'react'
import { motion } from 'motion/react'
export function MagneticButton({ children }: { children: React.ReactNode }) {
const ref = useRef<HTMLButtonElement>(null)
const [pos, setPos] = useState({ x: 0, y: 0 })
const onMove = (e: React.MouseEvent) => {
const { left, top, width, height } = ref.current!.getBoundingClientRect()
setPos({ x: (e.clientX - left - width / 2) * 0.3, y: (e.clientY - top - height / 2) * 0.3 })
}
return (
<motion.button ref={ref} onMouseMove={onMove} onMouseLeave={() => setPos({ x: 0, y: 0 })}
animate={pos} transition={{ type: 'spring', stiffness: 150, damping: 15 }}
className="px-8 py-4 bg-white text-black rounded-full">{children}</motion.button>
)
}
3. 视差主视觉(GSAP + useGSAP)
'use client'
import { useRef } from 'react'
import { gsap, ScrollTrigger, useGSAP } from '@/lib/gsap'
export function ParallaxHero() {
const containerRef = useRef<HTMLDivElement>(null)
useGSAP(() => {
gsap.to('.parallax-bg', {
yPercent: 50,
ease: 'none',
scrollTrigger: {
trigger: containerRef.current,
start: 'top top',
end: 'bottom top',
scrub: true,
},
})
gsap.to('.hero-title', {
yPercent: 100,
opacity: 0,
scrollTrigger: {
trigger: containerRef.current,
start: 'top top',
end: '50% top',
scrub: true,
},
})
}, { scope: containerRef })
return (
<div ref={containerRef} className="relative h-screen overflow-hidden">
<div className="parallax-bg absolute inset-0 bg-cover bg-center" />
<h1 className="hero-title absolute inset-0 flex items-center justify-center text-6xl">
Hero Title
</h1>
</div>
)
}
4. 逐字符文本显现(Motion)
'use client'
import { motion } from 'motion/react'
const container = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { staggerChildren: 0.02 },
},
}
const child = {
hidden: { opacity: 0, y: 50, rotateX: -90 },
visible: {
opacity: 1,
y: 0,
rotateX: 0,
transition: { type: 'spring', damping: 12 },
},
}
export function TextReveal({ text }: { text: string }) {
return (
<motion.span
variants={container}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
className="inline-block"
>
{text.split('').map((char, i) => (
<motion.span key={i} variants={child} className="inline-block">
{char === ' ' ? '\u00A0' : char}
</motion.span>
))}
</motion.span>
)
}
5. 图像显现(GSAP)
'use client'
import { useRef } from 'react'
import { gsap, useGSAP } from '@/lib/gsap'
export function ImageReveal({ src, alt }: { src: string; alt: string }) {
const containerRef = useRef<HTMLDivElement>(null)
useGSAP(() => {
gsap.from(containerRef.current, {
clipPath: 'inset(100% 0% 0% 0%)',
duration: 1.2,
ease: 'power4.inOut',
scrollTrigger: {
trigger: containerRef.current,
start: 'top 80%',
},
})
gsap.from('.reveal-img', {
scale: 1.3,
duration: 1.5,
ease: 'power2.out',
scrollTrigger: {
trigger: containerRef.current,
start: 'top 80%',
},
})
}, { scope: containerRef })
return (
<div ref={containerRef} className="overflow-hidden">
<img src={src} alt={alt} className="reveal-img w-full h-full object-cover" />
</div>
)
}
6. 故障文字效果(CSS + GSAP)
'use client'
import { useRef, useEffect } from 'react'
import { gsap } from '@/lib/gsap'
export function GlitchText({ text }: { text: string }) {
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
const layers = ref.current!.querySelectorAll('.g-layer')
const tl = gsap.timeline({ repeat: -1, repeatDelay: 3 })
tl.to(layers[0], { x: -5, duration: 0.05, ease: 'none' }, 0)
.to(layers[0], { x: 5, duration: 0.05 }, 0.05)
.to(layers[0], { x: 0, duration: 0.05 }, 0.1)
.to(layers[1], { x: 5, duration: 0.05 }, 0.02)
.to(layers[1], { x: -5, duration: 0.05 }, 0.07)
.to(layers[1], { x: 0, duration: 0.05 }, 0.12)
return () => { tl.kill() }
}, [])
return (
<div ref={ref} className="relative font-mono text-5xl font-black">
<span className="relative z-10">{text}</span>
<span className="g-layer absolute inset-0 text-cyan-400 mix-blend-multiply" aria-hidden>{text}</span>
<span className="g-layer absolute inset-0 text-red-400 mix-blend-multiply" aria-hidden>{text}</span>
</div>
)
}
7. 分形树(Canvas 2D)
'use client'
import { useRef, useEffect } from 'react'
export function FractalTree({ depth = 10, angle = 25 }: { depth?: number; angle?: number }) {
const canvasRef = useRef<HTMLCanvasElement>(null)
useEffect(() => {
const canvas = canvasRef.current!
const ctx = canvas.getContext('2d')!
canvas.width = canvas.offsetWidth * 2; canvas.height = canvas.offsetHeight * 2; ctx.scale(2, 2)
let progress = 0, raf = 0
function branch(x: number, y: number, len: number, a: number, d: number) {
if (d > depth || len < 2) return
const dp = Math.max(0, Math.min(1, progress * depth - d))
if (dp <= 0) return
const ex = x + Math.cos(a * Math.PI / 180) * len * dp
const ey = y - Math.sin(a * Math.PI / 180) * len * dp
ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(ex, ey)
ctx.strokeStyle = `hsl(${120 + d * 15}, 60%, ${30 + d * 5}%)`
ctx.lineWidth = Math.max(1, (depth - d) * 1.5); ctx.stroke()
branch(ex, ey, len * 0.72, a + angle, d + 1)
branch(ex, ey, len * 0.72, a - angle, d + 1)
}
const animate = () => {
progress = Math.min(1, progress + 0.008)
ctx.clearRect(0, 0, canvas.offsetWidth, canvas.offsetHeight)
branch(canvas.offsetWidth / 2, canvas.offsetHeight, canvas.offsetHeight * 0.28, 90, 0)
if (progress < 1) raf = requestAnimationFrame(animate)
}
animate()
return () => cancelAnimationFrame(raf)
}, [depth, angle])
return <canvas ref={canvasRef} className="w-full h-full bg-gray-950" />
}
有关 L 系统、流场、吸引子、噪声和神圣几何,请参阅 references/algorithmic-art.md。
8. 几何分割(SVG + GSAP)
'use client'
import { useRef, useState } from 'react'
import { gsap } from '@/lib/gsap'
const P = [
{ id: 'A', tri: 'M 0,173 L 50,87 L 100,173 Z', sq: 'M 0,0 L 100,0 L 100,87 L 0,87 Z', c: '#f43f5e' },
{ id: 'B', tri: 'M 50,87 L 100,0 L 150,87 Z', sq: 'M 100,0 L 200,0 L 200,87 L 100,87 Z', c: '#8b5cf6' },
{ id: 'C', tri: 'M 100,173 L 150,87 L 200,173 Z', sq: 'M 0,87 L 100,87 L 100,173 L 0,173 Z', c: '#06b6d4' },
{ id: 'D', tri: 'M 50,87 L 100,173 L 150,87 L 100,0 Z', sq: 'M 100,87 L 200,87 L 200,173 L 100,173 Z', c: '#f59e0b' },
]
export function GeometricDissection() {
const svg = useRef<SVGSVGElement>(null)
const [isSq, setSq] = useState(false)
const morph = () => {
const t = !isSq
P.forEach((p, i) => {
const el = svg.current!.querySelector(`#d-${p.id}`)
if (el) gsap.to(el, { attr: { d: t ? p.sq : p.tri }, duration: 1.5, ease: 'power2.inOut', delay: i * 0.15 })
}); setSq(t)
}
return (
<div className="flex flex-col items-center gap-4">
<svg ref={svg} viewBox="-10 -10 220 200" className="w-64 h-64">
{P.map(p => <path key={p.id} id={`d-${p.id}`} d={p.tri} fill={p.c} stroke="#000" strokeWidth="1.5" />)}
</svg>
<button onClick={morph} className="px-6 py-2 bg-white text-black font-mono text-sm">{isSq ? '△' : '□'}</button>
</div>
)
}
有关七巧板、镶嵌图案、彭罗斯拼砖和多格骨牌,请参阅 references/geometric-puzzles.md。
9. 粗野主义网格(动效)
'use client'
import { motion } from 'motion/react'
export function BrutalistGrid({ items }: { items: string[] }) {
return (
<div className="grid grid-cols-3 border-2 border-black">
{items.map((item, i) => (
<motion.div key={i}
className="border-2 border-black p-6 font-mono font-black uppercase text-2xl"
style={{ mixBlendMode: i % 2 === 0 ? 'normal' : 'difference' }}
initial={{ opacity: 0 }} whileInView={{ opacity: 1 }} viewport={{ once: true }}
transition={{ duration: 0, delay: i * 0.1 }}
whileHover={{ backgroundColor: '#000', color: '#BAFF39', transition: { duration: 0 } }}
>{item}</motion.div>
))}
</div>
)
}
设计理念(快速参考)
| 风格 | 动效感觉 | 缓动 | 字体排版 | 主要特点 |
|---|---|---|---|---|
| 粗野主义 | 生硬、即时、突兀 | none / steps() | 等宽字体,15-30vw | 坦率真实 |
| 极简主义 | 平滑、含蓄、缓慢 | power2.out | 轻盈的无衬线字体 | 有意克制 |
| 抽象主义 | 由噪声驱动、参数化 | 有机/正弦 | 不定 | 数学之美 |
| 新粗野主义 | 大胆但克制 | power1.out | 等宽字体 + 色彩 | 粗野主义 + 克制 |
完整指南及配色方案和混搭策略,请参阅 references/design-philosophy.md。
缓动参考
| 感觉 | GSAP | Motion |
|---|---|---|
| 平滑 | power2.out | [0.16, 1, 0.3, 1] |
| 干脆 | power4.out | [0.87, 0, 0.13, 1] |
| 弹跳 | back.out(1.7) | { type: 'spring', stiffness: 300, damping: 20 } |
| 戏剧性 | power4.inOut | [0.76, 0, 0.24, 1] |
时长
- 微交互:150-300ms
- UI 过渡:300-500ms
- 页面过渡:500-800ms
- 交错间隔:每项 0.02-0.1s
无障碍
// Motion: useReducedMotion() → conditionally disable/reduce animations
import { useReducedMotion } from 'motion/react'
const reduced = useReducedMotion() // true if prefers-reduced-motion: reduce
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}
性能规则
- 只为
transform和opacity添加动画 - 谨慎使用
will-change - 始终清理:
useGSAP会自动处理 - 将 GSAP 选择器限定在容器引用范围内
- 在使用 GSAP 的事件处理器中使用
contextSafe() - 对 Motion 变体对象进行记忆化
常见问题
- 未将 Lenis 与 ScrollTrigger 集成
- 在 useGSAP 中缺少
scope - 点击处理器未使用
contextSafe() - React 18 Strict Mode 会调用两次 effects
- 忘记在 Next.js App Router 中添加
'use client' - 动态内容更新后未调用
ScrollTrigger.refresh()
测试清单
- 滚动时达到 60fps(Chrome DevTools Performance)
- 键盘导航正常
- 遵循 prefers-reduced-motion 设置
- 没有布局偏移(CLS)
- 移动设备触摸操作正常
- 生产环境中已移除 ScrollTrigger 标记
- 卸载时没有内存泄漏
灵感来源
Active Theory、Studio Freight、Locomotive、Resn、Aristide Benoist、Immersive Garden
评论
支持 Markdown