elite-performance について
アニメーションと Core Web Vitals の最適化に取り組む Web 開発者向けのパフォーマンススキルです。実装、読み込み、メモリのクリーンアップ、デバッグの指針をまとめています。
主な機能
- 01Core Web Vitals とアニメーションの目標
- 02GPU 向けのアニメーションプロパティ
- 03GSAP のクリーンアップとバッチ処理
- 04読み込み戦略とアセット最適化
- 05パフォーマンスデバッグのチェックリスト
活用シーン
- 01レイアウトを発生させるアニメーションの確認
- 02アニメーションライブラリの読み込み計画
- 03フレーム落ちやメモリリークの調査
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ rshvr/ elite-web-design --skill elite-performanceインストール後、Claude Code で/elite-performanceを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/animation-performance.mdasset-optimization.mddebugging.mdvite-setup.md詳細
エリートパフォーマンス
Core Web Vitals の目標を達成しながら、60fps のアニメーションを維持します。
クイックリファレンス
| トピック | 参照ファイル |
|---|---|
| Vite の設定 | vite-setup.md |
| アニメーションのパフォーマンス | animation-performance.md |
| アセットの最適化 | asset-optimization.md |
| デバッグ | debugging.md |
パフォーマンス予算(2026)
Core Web Vitals の目標
| 指標 | 良好 | 要改善 | 不良 |
|---|---|---|---|
| LCP(Largest Contentful Paint) | ≤ 2.5s | ≤ 4.0s | > 4.0s |
| INP(Interaction to Next Paint) | ≤ 200ms | ≤ 500ms | > 500ms |
| CLS(Cumulative Layout Shift) | ≤ 0.1 | ≤ 0.25 | > 0.25 |
アニメーションの目標
| 指標 | 目標 |
|---|---|
| フレームレート | 60fps(16.67ms/フレーム) |
| フレーム予算 | JS/レイアウトで < 10ms |
| アニメーション開始 | 応答まで < 100ms |
| スクロールのカクつき | フレームのドロップ 0 件 |
バンドルの目標
| アセット | 目標 |
|---|---|
| 初期 JS | < 100KB(gzip 圧縮後) |
| 初期 CSS | < 50KB(gzip 圧縮後) |
| GSAP core | 約 25KB(gzip 圧縮後) |
| 初期合計 | < 200KB(gzip 圧縮後) |
GPU アクセラレーション対応のプロパティ
アニメーションするのはこれだけ
/* GPU composited - FAST */
transform: translateX() translateY() translateZ() scale() rotate() skew();
opacity: 0 to 1;
filter: blur() brightness() contrast();
/* Will trigger compositor layer */
will-change: transform, opacity;
これらはアニメーションしない
/* Triggers layout - SLOW */
width, height
top, right, bottom, left
margin, padding
font-size
border-width
/* Triggers paint - SLOW */
background-color, color
border-color
box-shadow
text-shadow
Transform と Position の比較
/* BAD - Triggers layout every frame */
.element {
animation: moveLeft 1s;
}
@keyframes moveLeft {
to {
left: 100px;
}
}
/* GOOD - GPU composited */
.element {
animation: moveLeft 1s;
}
@keyframes moveLeft {
to {
transform: translateX(100px);
}
}
すぐに効果が出るパフォーマンス改善
1. ファーストビューより下のコンテンツを遅延読み込みする
<img src="hero.jpg" alt="Hero" loading="eager" />
<img src="feature.jpg" alt="Feature" loading="lazy" />
<div class="lazy-section" data-component="heavy-animation">
</div>
2. content-visibility を使う
/* Skip rendering off-screen sections */
.section {
content-visibility: auto;
contain-intrinsic-size: 0 500px; /* Estimated height */
}
3. コストの高いエフェクトを制限する
/* Isolate animated sections */
.animated-section {
contain: layout style paint;
}
/* Full containment for cards */
.card {
contain: strict;
}
4. 適切な場合はモーションを減らす
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
5. スクロールハンドラーを最適化する
// BAD - Fires on every scroll event
window.addEventListener("scroll", handleScroll);
// GOOD - Passive listener, no preventDefault
window.addEventListener("scroll", handleScroll, { passive: true });
// BETTER - Use ScrollTrigger (already optimized)
gsap.to(".element", {
scrollTrigger: {/* ... */},
});
GSAP のパフォーマンス
クリーンアップには gsap.context() を使う
// Prevents memory leaks
const ctx = gsap.context(() => {
gsap.to(".element", { x: 100 });
ScrollTrigger.create({/* ... */});
});
// On unmount
ctx.revert();
ScrollTrigger の更新をまとめて処理する
// Process multiple items efficiently
ScrollTrigger.batch(".item", {
onEnter: (batch) =>
gsap.to(batch, {
opacity: 1,
y: 0,
stagger: 0.1,
}),
});
refreshPriority を使う
// Control refresh order
ScrollTrigger.create({
trigger: ".section",
refreshPriority: -1, // Refresh after others
});
ScrollTrigger を遅延読み込みする
// Don't create all at once
const createTrigger = (element) => {
ScrollTrigger.create({
trigger: element,
start: "top 80%",
onEnter: () => {
// Create animation only when needed
gsap.from(element, { opacity: 0, y: 50 });
},
once: true,
});
};
// Create triggers as needed
gsap.utils.toArray(".section").forEach(createTrigger);
CSS アニメーションのパフォーマンス
効率的なキーフレーム
/* GOOD - Only compositor properties */
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* BAD - Triggers layout */
@keyframes slideIn {
from {
opacity: 0;
margin-top: 30px;
}
to {
opacity: 1;
margin-top: 0;
}
}
will-change のベストプラクティス
/* Apply just before animation */
.card {
transition:
transform 0.3s,
opacity 0.3s;
}
.card:hover {
will-change: transform;
}
/* Remove after animation */
.card.animating {
will-change: transform, opacity;
}
/* Never do this globally */
/* * { will-change: transform; } NEVER! */
コンポジットレイヤー
/* Force new layer when needed */
.animated-element {
transform: translateZ(0); /* or translate3d(0,0,0) */
}
/* Better: Use will-change temporarily */
.animating {
will-change: transform;
}
読み込み戦略
クリティカルパス
<head>
<style>
/* Above-fold styles */
</style>
<link rel="preload" href="hero.webp" as="image" />
<link rel="preload" href="font.woff2" as="font" crossorigin />
<link
rel="stylesheet"
href="full.css"
media="print"
onload="this.media='all'"
/>
</head>
<body>
<script src="gsap.min.js" defer></script>
<script src="app.js" defer></script>
</body>
動的インポート
// Load GSAP plugins only when needed
const loadScrollTrigger = async () => {
const { ScrollTrigger } = await import("gsap/ScrollTrigger");
gsap.registerPlugin(ScrollTrigger);
return ScrollTrigger;
};
// Load on interaction or visibility
const section = document.querySelector(".scroll-section");
const observer = new IntersectionObserver(async ([entry]) => {
if (entry.isIntersecting) {
await loadScrollTrigger();
initScrollAnimations();
observer.disconnect();
}
});
observer.observe(section);
プログレッシブエンハンスメント
// Check for animation support
const supportsAnimation = "animate" in document.documentElement;
const prefersReducedMotion = window.matchMedia(
"(prefers-reduced-motion: reduce)",
).matches;
if (supportsAnimation && !prefersReducedMotion) {
// Load animation library
import("./animations.js");
} else {
// Show final states immediately
document.querySelectorAll(".animated").forEach((el) => {
el.classList.add("animation-complete");
});
}
メモリ管理
アニメーションをクリーンアップする
// Store references for cleanup
const animations = [];
const scrollTriggers = [];
function initAnimations() {
animations.push(gsap.to(".element", { x: 100 }));
scrollTriggers.push(ScrollTrigger.create({ trigger: ".section" }));
}
function cleanup() {
animations.forEach((anim) => anim.kill());
scrollTriggers.forEach((st) => st.kill());
animations.length = 0;
scrollTriggers.length = 0;
}
// Or use gsap.context()
const ctx = gsap.context(() => {
// All animations here
});
// Cleanup
ctx.revert();
SplitText のクリーンアップ
const splits = [];
function initTextAnimations() {
document.querySelectorAll(".split-text").forEach((el) => {
const split = new SplitText(el, { type: "chars" });
splits.push(split);
gsap.from(split.chars, {
opacity: 0,
y: 20,
stagger: 0.02,
});
});
}
function cleanup() {
splits.forEach((split) => split.revert());
splits.length = 0;
}
イベントリスナーのクリーンアップ
// Use AbortController for easy cleanup
const controller = new AbortController();
window.addEventListener("resize", handleResize, {
signal: controller.signal,
});
window.addEventListener("scroll", handleScroll, {
passive: true,
signal: controller.signal,
});
// Cleanup all at once
function cleanup() {
controller.abort();
}
デバッグチェックリスト
パフォーマンスの問題
-
フレームがドロップする場合
- DevTools の Performance パネルを確認する
- 長時間タスク(> 50ms)がないか確認する
- コンポジター対応プロパティだけがアニメーションしていることを確認する
-
初回読み込みが遅い場合
- Network のウォーターフォールを確認する
- クリティカルパスが最適化されていることを確認する
- バンドルサイズを監査する
-
メモリーリークがある場合
- 時間経過に伴う Memory パネルの変化を確認する
- ナビゲーション時にクリーンアップされることを確認する
- 分離された DOM ノードに注意する
-
レイアウトスラッシングがある場合
- Performance で強制リフローがないか確認する
- DOM の読み取り/書き込みをまとめる
- position の代わりに transform を使う
簡易チェック
// Log animation frame rate
let lastTime = performance.now();
let frameCount = 0;
function measureFPS() {
frameCount++;
const now = performance.now();
if (now - lastTime >= 1000) {
console.log("FPS:", frameCount);
frameCount = 0;
lastTime = now;
}
requestAnimationFrame(measureFPS);
}
measureFPS();
// Detect layout thrashing
const originalGetComputedStyle = window.getComputedStyle;
window.getComputedStyle = function (...args) {
console.trace("getComputedStyle called");
return originalGetComputedStyle.apply(this, args);
};
包括的なデバッグ手法については、debugging.md を参照してください。
リソース
- web.dev のパフォーマンス
- Chrome DevTools のパフォーマンス
- GSAP のパフォーマンスに関するヒント
- CSS Triggers - レイアウト/ペイントを発生させるプロパティについて
コメント
Markdown 対応