gsap について
gsapは、JARVIS AI Assistant HUDの開発者向けGSAPアニメーションスキルです。テスト先行、アニメーションの後処理、性能、動きを抑える設定への対応を重視します。
主な機能
- 01HUDパネルの表示・終了アニメーション
- 02ステータスとデータ可視化の演出
- 03スクロール連動効果とタイムライン
- 04アニメーションのテストと破棄時の後処理
- 05動きを抑える設定への対応
活用シーン
- 01HUDパネルの表示・終了効果の追加
- 02ステータスインジケーターの遷移演出
- 03スクロール連動ページアニメーションの作成
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ martinholovsky/ claude-skills-generator --skill gsapインストール後、Claude Code で/gsapを入力して読み込まれたか確認します。
同梱ファイルreferences/advanced-patterns.md
詳細
GSAP アニメーションスキル
ファイル構成:このスキルは分割構造を使用します。高度なパターンについては
references/を参照してください。
1. 概要
このスキルでは、JARVIS AI Assistant HUD で滑らかでプロフェッショナルなアニメーションを作成するための GSAP(GreenSock Animation Platform)の専門知識を提供します。
リスクレベル:低 — セキュリティ上の攻撃対象領域が最小限のアニメーションライブラリです。
主なユースケース:
- HUD パネルの表示/非表示アニメーション
- ステータスインジケーターの遷移
- データビジュアライゼーションのアニメーション
- スクロール連動エフェクト
- 複雑なタイムラインシーケンス
2. 主な責務
2.1 基本原則
- まず TDD:実装前にアニメーションのテストを作成します
- パフォーマンスを考慮:GPU アクセラレーションには transform/opacity を使用し、レイアウトのスラッシングを避けます
- クリーンアップ必須:コンポーネントのアンマウント時には、必ずアニメーションを停止します
- タイムラインの整理:複雑なシーケンスにはタイムラインを使用します
- イージングの選択:HUD の雰囲気に合ったイージングを選びます
- アクセシビリティ:動きを減らす設定を尊重します
- メモリ管理:適切にクリーンアップし、メモリリークを防ぎます
2.5 実装ワークフロー(TDD)
ステップ 1:最初に失敗するテストを書く
// tests/animations/panel-animation.test.ts
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { mount } from '@vue/test-utils'
import { gsap } from 'gsap'
import HUDPanel from '~/components/HUDPanel.vue'
describe('HUDPanel Animation', () => {
beforeEach(() => {
// Mock reduced motion
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation(query => ({
matches: false,
media: query
}))
})
})
afterEach(() => {
// Verify cleanup
gsap.globalTimeline.clear()
})
it('animates panel entrance with correct properties', async () => {
const wrapper = mount(HUDPanel)
// Wait for animation to complete
await new Promise(resolve => setTimeout(resolve, 600))
const panel = wrapper.find('.hud-panel')
expect(panel.exists()).toBe(true)
})
it('cleans up animations on unmount', async () => {
const wrapper = mount(HUDPanel)
const childCount = gsap.globalTimeline.getChildren().length
await wrapper.unmount()
// All animations should be killed
expect(gsap.globalTimeline.getChildren().length).toBeLessThan(childCount)
})
it('respects reduced motion preference', async () => {
// Mock reduced motion enabled
window.matchMedia = vi.fn().mockImplementation(() => ({
matches: true
}))
const wrapper = mount(HUDPanel)
const panel = wrapper.find('.hud-panel').element
// Should set final state immediately without animation
expect(gsap.getProperty(panel, 'opacity')).toBe(1)
})
})
ステップ 2:テストに合格する最小限の実装を行う
// components/HUDPanel.vue - implement animation logic
const animation = ref<gsap.core.Tween | null>(null)
onMounted(() => {
if (!panelRef.value) return
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
gsap.set(panelRef.value, { opacity: 1 })
return
}
animation.value = gsap.from(panelRef.value, {
opacity: 0,
y: 20,
duration: 0.5
})
})
onUnmounted(() => {
animation.value?.kill()
})
ステップ 3:パターンに沿ってリファクタリングする
// Extract to composable for reusability
export function usePanelAnimation(elementRef: Ref<HTMLElement | null>) {
const animation = ref<gsap.core.Tween | null>(null)
const animate = () => {
if (!elementRef.value) return
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
gsap.set(elementRef.value, { opacity: 1 })
return
}
animation.value = gsap.from(elementRef.value, {
opacity: 0,
y: 20,
duration: 0.5,
ease: 'power2.out'
})
}
onMounted(animate)
onUnmounted(() => animation.value?.kill())
return { animation }
}
ステップ 4:完全な検証を実行する
# Run animation tests
npm test -- --grep "Animation"
# Check for memory leaks
npm run test:memory
# Verify 60fps performance
npm run test:performance
3. 技術スタックとバージョン
3.1 推奨バージョン
| パッケージ | バージョン | 備考 |
|---|---|---|
| gsap | ^3.12.0 | コアライブラリ |
| @gsap/vue | ^3.12.0 | Vue 連携 |
| ScrollTrigger | 含まれています | スクロールエフェクト |
3.2 Vue 連携
// plugins/gsap.ts
import { gsap } from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
export default defineNuxtPlugin(() => {
gsap.registerPlugin(ScrollTrigger)
return {
provide: {
gsap,
ScrollTrigger
}
}
})
4. 実装パターン
4.1 パネルの表示アニメーション
<script setup lang="ts">
import { gsap } from 'gsap'
import { onMounted, onUnmounted, ref } from 'vue'
const panelRef = ref<HTMLElement | null>(null)
let animation: gsap.core.Tween | null = null
onMounted(() => {
if (!panelRef.value) return
// ✅ Check reduced motion preference
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
gsap.set(panelRef.value, { opacity: 1 })
return
}
animation = gsap.from(panelRef.value, {
opacity: 0,
y: 20,
scale: 0.95,
duration: 0.5,
ease: 'power2.out'
})
})
// ✅ Cleanup on unmount
onUnmounted(() => {
animation?.kill()
})
</script>
<template>
<div ref="panelRef" class="hud-panel">
<slot />
</div>
</template>
4.2 ステータスインジケーターのアニメーション
// composables/useStatusAnimation.ts
import { gsap } from 'gsap'
export function useStatusAnimation(element: Ref<HTMLElement | null>) {
const timeline = ref<gsap.core.Timeline | null>(null)
const animateStatus = (status: string) => {
if (!element.value) return
timeline.value?.kill()
timeline.value = gsap.timeline()
switch (status) {
case 'active':
timeline.value
.to(element.value, {
scale: 1.2,
duration: 0.2,
ease: 'power2.out'
})
.to(element.value, {
scale: 1,
duration: 0.3,
ease: 'elastic.out(1, 0.3)'
})
break
case 'warning':
timeline.value.to(element.value, {
backgroundColor: '#f59e0b',
boxShadow: '0 0 10px #f59e0b',
duration: 0.3,
repeat: 2,
yoyo: true
})
break
case 'error':
timeline.value.to(element.value, {
x: -5,
duration: 0.05,
repeat: 5,
yoyo: true
})
break
}
}
onUnmounted(() => {
timeline.value?.kill()
})
return { animateStatus }
}
4.3 データビジュアライゼーションのアニメーション
<script setup lang="ts">
import { gsap } from 'gsap'
const props = defineProps<{
data: number[]
}>()
const barsRef = ref<HTMLElement[]>([])
let animations: gsap.core.Tween[] = []
watch(() => props.data, (newData) => {
// Kill previous animations
animations.forEach(a => a.kill())
animations = []
// Animate each bar
newData.forEach((value, index) => {
const bar = barsRef.value[index]
if (!bar) return
const tween = gsap.to(bar, {
height: `${value}%`,
duration: 0.5,
delay: index * 0.05,
ease: 'power2.out'
})
animations.push(tween)
})
}, { immediate: true })
onUnmounted(() => {
animations.forEach(a => a.kill())
})
</script>
<template>
<div class="flex items-end h-40 gap-1">
<div
v-for="(_, index) in data"
:key="index"
ref="barsRef"
class="w-4 bg-jarvis-primary"
/>
</div>
</template>
4.4 タイムラインシーケンス
// Create complex HUD startup sequence
export function createStartupSequence(elements: {
logo: HTMLElement
panels: HTMLElement[]
status: HTMLElement
}): gsap.core.Timeline {
const tl = gsap.timeline({
defaults: { ease: 'power2.out' }
})
// Logo reveal
tl.from(elements.logo, {
opacity: 0,
scale: 0,
duration: 0.8,
ease: 'back.out(1.7)'
})
// Panels stagger in
tl.from(elements.panels, {
opacity: 0,
x: -30,
stagger: 0.1,
duration: 0.5
}, '-=0.3')
// Status indicator
tl.from(elements.status, {
opacity: 0,
y: 10,
duration: 0.3
}, '-=0.2')
return tl
}
4.5 スクロール連動アニメーション
<script setup lang="ts">
import { gsap } from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
const sectionRef = ref<HTMLElement | null>(null)
onMounted(() => {
if (!sectionRef.value) return
gsap.from(sectionRef.value.querySelectorAll('.animate-item'), {
scrollTrigger: {
trigger: sectionRef.value,
start: 'top 80%',
end: 'bottom 20%',
toggleActions: 'play none none reverse'
},
opacity: 0,
y: 30,
stagger: 0.1,
duration: 0.5
})
})
onUnmounted(() => {
ScrollTrigger.getAll().forEach(trigger => trigger.kill())
})
</script>
5. 品質基準
5.1 パフォーマンス
// ✅ GOOD - Use transforms for GPU acceleration
gsap.to(element, {
x: 100,
y: 50,
rotation: 45,
scale: 1.2
})
// ❌ BAD - Triggers layout recalculation
gsap.to(element, {
left: 100,
top: 50,
width: '120%'
})
5.2 アクセシビリティ
// ✅ Respect reduced motion
const prefersReducedMotion = window.matchMedia(
'(prefers-reduced-motion: reduce)'
).matches
if (prefersReducedMotion) {
gsap.set(element, { opacity: 1 })
} else {
gsap.from(element, { opacity: 0, duration: 0.5 })
}
6. パフォーマンスパターン
6.1 will-change プロパティの使用
// Good: Apply will-change before animation
const animatePanel = (element: HTMLElement) => {
element.style.willChange = 'transform, opacity'
gsap.to(element, {
x: 100,
opacity: 0.8,
duration: 0.5,
onComplete: () => {
element.style.willChange = 'auto'
}
})
}
// Bad: Never removing will-change
const animatePanelBad = (element: HTMLElement) => {
element.style.willChange = 'transform, opacity' // Memory leak!
gsap.to(element, { x: 100, opacity: 0.8 })
}
6.2 transform とレイアウトプロパティの比較
// Good: Use transforms (GPU accelerated)
gsap.to(element, {
x: 100, // translateX
y: 50, // translateY
scale: 1.2, // scale
rotation: 45, // rotate
opacity: 0.5 // opacity
})
// Bad: Layout-triggering properties (CPU, causes reflow)
gsap.to(element, {
left: 100, // Triggers layout
top: 50, // Triggers layout
width: '120%', // Triggers layout
height: 200, // Triggers layout
margin: 10 // Triggers layout
})
6.3 タイムラインの再利用
// Good: Reuse timeline instance
const timeline = gsap.timeline({ paused: true })
timeline
.to(element, { opacity: 1, duration: 0.3 })
.to(element, { y: -20, duration: 0.5 })
// Play/reverse as needed
const show = () => timeline.play()
const hide = () => timeline.reverse()
// Bad: Creating new timeline each time
const showBad = () => {
gsap.timeline()
.to(element, { opacity: 1, duration: 0.3 })
.to(element, { y: -20, duration: 0.5 })
}
6.4 ScrollTrigger のバッチ処理
// Good: Batch ScrollTrigger animations
ScrollTrigger.batch('.animate-item', {
onEnter: (elements) => {
gsap.to(elements, {
opacity: 1,
y: 0,
stagger: 0.1,
overwrite: true
})
},
onLeave: (elements) => {
gsap.to(elements, {
opacity: 0,
y: -20,
overwrite: true
})
}
})
// Bad: Individual ScrollTrigger per element
document.querySelectorAll('.animate-item').forEach(item => {
gsap.to(item, {
scrollTrigger: {
trigger: item,
start: 'top 80%'
},
opacity: 1,
y: 0
})
})
6.5 遅延初期化
// Good: Initialize animations only when needed
let panelAnimation: gsap.core.Timeline | null = null
const getPanelAnimation = () => {
if (!panelAnimation) {
panelAnimation = gsap.timeline({ paused: true })
.from('.panel', { opacity: 0, y: 20 })
.from('.panel-content', { opacity: 0, stagger: 0.1 })
}
return panelAnimation
}
const showPanel = () => getPanelAnimation().play()
const hidePanel = () => getPanelAnimation().reverse()
// Bad: Initialize all animations on mount
onMounted(() => {
// Creates timeline even if never used
const animation1 = gsap.timeline().to('.panel1', { x: 100 })
const animation2 = gsap.timeline().to('.panel2', { y: 100 })
const animation3 = gsap.timeline().to('.panel3', { scale: 1.2 })
})
7. テストと品質
7.1 アニメーションのテスト
describe('Panel Animation', () => {
it('cleans up on unmount', async () => {
const wrapper = mount(HUDPanel)
await wrapper.unmount()
// No active GSAP animations should remain
expect(gsap.globalTimeline.getChildren().length).toBe(0)
})
})
8. よくある間違いとアンチパターン
8.1 重大なアンチパターン
禁止:クリーンアップを省略する
// ❌ MEMORY LEAK
onMounted(() => {
gsap.to(element, { x: 100, duration: 1 })
})
// ✅ PROPER CLEANUP
let tween: gsap.core.Tween
onMounted(() => {
tween = gsap.to(element, { x: 100, duration: 1 })
})
onUnmounted(() => {
tween?.kill()
})
禁止:レイアウトプロパティをアニメーションさせる
// ❌ BAD - Causes layout thrashing
gsap.to(element, { width: 200, height: 100 })
// ✅ GOOD - Use transforms
gsap.to(element, { scaleX: 2, scaleY: 1 })
13. 実装前チェックリスト
フェーズ 1:コードを書く前
- アニメーションの動作に関する失敗するテストを書く
- アニメーションのタイミングとイージングの要件を定義する
- will-change の指定が必要な要素を特定する
- すべてのアニメーションのクリーンアップ方法を計画する
- 動きを減らす設定への対応が必要か確認する
フェーズ 2:実装中
- transform/opacity のみを使用する(レイアウトプロパティは使用しない)
- クリーンアップ用にアニメーションの参照を保持する
- アニメーションの前に will-change を適用し、終了後に削除する
- シーケンスにはタイムラインを使用する
- ScrollTrigger のアニメーションをまとめて処理する
- 複雑なアニメーションには遅延初期化を実装する
フェーズ 3:コミット前
- すべてのテストに合格する(npm test -- --grep "Animation")
- アンマウント時にすべてのアニメーションがクリーンアップされる
- 動きを減らす設定が尊重される
- メモリリークがない(DevTools で確認する)
- 60fps を維持する(パフォーマンスモニターでテストする)
- ScrollTrigger のインスタンスが適切に停止される
14. まとめ
GSAP は JARVIS HUD にプロフェッショナルなアニメーションを提供します。
- クリーンアップ:アンマウント時には必ずアニメーションを停止する
- パフォーマンス:transform と opacity のみを使用する
- アクセシビリティ:動きを減らす設定を尊重する
- 整理:シーケンスにはタイムラインを使用する
注意:メモリリークを防ぐため、すべてのアニメーションをクリーンアップする必要があります。
参考資料:
references/advanced-patterns.md- 複雑なアニメーションパターン
コメント
Markdown 対応