Web Animations API と高性能アニメーション:GPU 加速と composite 最適化

性能优化(更新: 2026年6月6日)

ブラウザレンダリングパイプラインとアニメーション性能

1フレームのレンダリングは Style → Layout → Paint → Composite の順で処理されます。アニメーション性能の鍵は可能な限り多くの段階をスキップすることです:

プロパティ種別 発生段階 性能
width/height/margin Layout → Paint → Composite
color/background Paint → Composite
transform/opacity Composite のみ

transformopacity だけが Layout と Paint をスキップし、GPU の Composite レイヤーで直接合成できます。


GPU 加速:composite レイヤーの仕組み

合成レイヤーへの昇格条件

以下の条件でブラウザは要素を独立した合成レイヤーに昇格させます:

  • transform: translateZ(0) または will-change: transform
  • <video><canvas>、WebGL コンテンツ
  • transform または opacity に対する CSS animation/transition
  • position: fixed(一部ブラウザ)

合成レイヤーのコスト

各合成レイヤー = 独立した GPU テクスチャ = VRAM 消費

レイヤー過多 → VRAM 圧迫 → 逆に性能低下

推奨:合成レイヤー数は 30 以下に抑える。モバイルではさらに少なく。


will-change:正しい使い方とよくある誤解

基本的な使い方

.animating-element {
  will-change: transform, opacity;
}

ベストプラクティス

/* 悪い例:グローバルに宣言、リソースの無駄遣い */
.card {
  will-change: transform;
}

/* 良い例:インタラクション直前にのみ宣言 */
.card:hover {
  will-change: transform;
}
.card.is-dragging {
  will-change: transform;
}
// JS で will-change を動的制御
element.addEventListener('mouseenter', () => {
  element.style.willChange = 'transform';
});
element.addEventListener('transitionend', () => {
  element.style.willChange = 'auto';
});

原則:will-change は「最適化スイッチ」ではなく「予告」です。アニメーション直前にのみ設定し、終了後に削除します。


Web Animations API:element.animate()

基本構文

const animation = element.animate(
  [
    { transform: 'translateX(0px)', opacity: 1 },
    { transform: 'translateX(300px)', opacity: 0.5 }
  ],
  {
    duration: 600,
    easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
    fill: 'forwards',
    iterations: 1
  }
);

CSS Animations との比較

機能 CSS Animations Web Animations API
宣言方法 CSS @keyframes JS element.animate()
動的制御 困難(クラス操作が必要) 簡単(play()/pause()/reverse()
動的キーフレーム 非対応 実行時生成に対応
Promise 統合 なし animation.finished/animation.ready
性能 同じ 同じ(同じエンジン)
ブラウザ最適化 事前解析可能 実行時最適化

アニメーション制御 API

const anim = element.animate(keyframes, options);

anim.pause();
anim.play();
anim.reverse();
anim.finish();
anim.cancel();

anim.playbackRate = 2.0;
anim.currentTime = 300;

anim.finished.then(() => {
  console.log('アニメーション完了');
});

requestAnimationFrame とアニメーションスケジューリング

なぜ setTimeout/setInterval ではダメなのか

// 悪い例:画面リフレッシュと非同期、フレームドロップの可能性
setInterval(() => {
  element.style.transform = `translateX(${x}px)`;
}, 16);

// 良い例:ブラウザのリフレッシュレートと同期
function animate(timestamp) {
  const progress = (timestamp - startTime) / duration;
  element.style.transform = `translateX(${targetX * progress}px)`;
  if (progress < 1) requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

rAF と WAAPI の組み合わせ

// 複雑なシナリオ:WAAPI で単純アニメーション、rAF でフレーム単位の計算
const scrollAnim = element.animate(
  [{ transform: 'translateY(0)' }, { transform: 'translateY(-100px)' }],
  { duration: 300, fill: 'forwards' }
);

// スクロール駆動アニメーションには rAF が必要
let ticking = false;
window.addEventListener('scroll', () => {
  if (!ticking) {
    requestAnimationFrame(() => {
      element.style.transform = `translateY(${window.scrollY * 0.5}px)`;
      ticking = false;
    });
    ticking = true;
  }
});

Composite レイヤー最適化の実践

再描画領域の削減

/* 悪い例:コンテナ全体が再描画 */
.container {
  animation: pulse 1s infinite;
}
@keyframes pulse {
  0%, 100% { background: #fff; }
  50% { background: #f0f0f0; }
}

/* 良い例:アニメーションレイヤーのみ再描画 */
.container {
  position: relative;
}
.container::after {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0);
  animation: pulse 1s infinite;
  will-change: opacity;
}
@keyframes pulse {
  0%, 100% { opacity: 0; }
  50% { opacity: 0.05; }
}

レイヤー爆発の回避

/* 悪い例:全リスト項目が合成レイヤーを作成 */
.list-item {
  will-change: transform;
}

/* 良い例:アニメーション中の要素のみ昇格 */
.list-item.is-animating {
  will-change: transform;
}

contain プロパティによる隔離

.card {
  contain: layout style paint;
}

contain はブラウザに「この要素のスタイル/レイアウト/描画は外部に影響しない」ことを伝え、安全な最適化を可能にします。


性能計測

Chrome DevTools Layers パネル

  1. DevTools → More tools → Layers を開く
  2. 合成レイヤー数と VRAM 使用量を確認
  3. 不要なレイヤー昇格を特定

Performance パネル分析

フレームタイムライン:
- 緑 = Paint
- 紫 = Layout
- 透明 = Composite ← 理想的な状態

FPS 検出

let lastTime = performance.now();
let frames = 0;

function measureFPS() {
  frames++;
  const now = performance.now();
  if (now - lastTime >= 1000) {
    console.log(`FPS: ${frames}`);
    frames = 0;
    lastTime = now;
  }
  requestAnimationFrame(measureFPS);
}
requestAnimationFrame(measureFPS);

最適化チェックリスト

  • アニメーションは transformopacity のみ使用
  • width/height/top/left のアニメーションを避ける
  • will-change はアニメーション前に設定、終了後に削除
  • 合成レイヤー数 < 30
  • contain: layout style paint で再描画範囲を隔離
  • 複雑なアニメーションには Web Animations API で詳細制御
  • スクロール駆動アニメーションは requestAnimationFrame でスロットル

まとめ

高性能アニメーションの核心はGPU に得意なことをさせることです。transformopacity のみアニメーションし、will-change でブラウザに予告し、合成レイヤー数を制御して VRAM の無駄を防ぎます。Web Animations API は CSS アニメーションと同じ性能基盤を提供しながら、JS レベルのきめ細かな制御をもたらします。

CSSアニメーションツール でアニメーション効果をプレビュー、グラデーションジェネレーター で高性能な背景アニメーションを作成、画像変換 で画像の transform 操作を行えます。

ブラウザローカルツールを無料で試す →

#Web Animations API#动画性能#requestAnimationFrame#GPU加速#composite