mcpField
E
elite-performance

elite-performance

github.com/rshvr開発140 回インストール10 週間前に更新
リポジトリ

elite-performance について

アニメーションと Core Web Vitals の最適化に取り組む Web 開発者向けのパフォーマンススキルです。実装、読み込み、メモリのクリーンアップ、デバッグの指針をまとめています。

主な機能

  1. 01Core Web Vitals とアニメーションの目標
  2. 02GPU 向けのアニメーションプロパティ
  3. 03GSAP のクリーンアップとバッチ処理
  4. 04読み込み戦略とアセット最適化
  5. 05パフォーマンスデバッグのチェックリスト

活用シーン

  1. 01レイアウトを発生させるアニメーションの確認
  2. 02アニメーションライブラリの読み込み計画
  3. 03フレーム落ちやメモリリークの調査

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/rshvr/elite-web-design --skill elite-performance

インストール後、Claude Code で/elite-performanceを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
animation-performance.md
asset-optimization.md
debugging.md
vite-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();
}

デバッグチェックリスト

パフォーマンスの問題
  1. フレームがドロップする場合

    • DevTools の Performance パネルを確認する
    • 長時間タスク(> 50ms)がないか確認する
    • コンポジター対応プロパティだけがアニメーションしていることを確認する
  2. 初回読み込みが遅い場合

    • Network のウォーターフォールを確認する
    • クリティカルパスが最適化されていることを確認する
    • バンドルサイズを監査する
  3. メモリーリークがある場合

    • 時間経過に伴う Memory パネルの変化を確認する
    • ナビゲーション時にクリーンアップされることを確認する
    • 分離された DOM ノードに注意する
  4. レイアウトスラッシングがある場合

    • 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 を参照してください。


リソース

コメント