世界を動かす技術を、日本語で。

高さをアニメーションしない

2025年7月20日原文(granola.ai)

概要

Granolaアプリで 高いCPU・GPU使用率 の原因が 小さなCSSアニメーション であることを発見。 Chrome DevTools で高コストなアニメーションの特定方法と最適化手法を解説。 height などのレイアウトプロパティのアニメーションが特に高コストである理由を説明。 transform を使ったアニメーション最適化による大幅なパフォーマンス改善例を紹介。 ブラウザの レンダリングパイプライン 理解がパフォーマンス向上に不可欠であることを強調。

Granolaアプリの高負荷問題と原因特定

  • Granolaは Electron製ノートアプリ
  • M2 MacBook でCPU 60%、GPU 25%使用を確認
  • Chrome DevTools のPerformanceタブで、主な処理はJavaScriptでなく「Rendering」と「Painting」
  • Layersタブで「アクションバー」レイヤーが毎フレーム再描画されている
  • Renderingタブで「Paint Flashing」「Layout Shift Regions」を有効化し、 音量ビジュアライザー のアニメーションが原因と判明
  • ビジュアライザーの「踊るバー」3本が常に再レイアウト・再描画される現象
  • DOM更新頻度を下げても効果なし。 CSSアニメーション がボトルネック
  • Performanceタブで「Animation」ジョブ3つ(バーごとに1つ)を確認
  • 問題のCSS:transition: height 300ms ease-in-out;

ブラウザのレンダリングパイプラインとアニメーションコスト

  • レンダリングパイプラインは Layout → Paint → Composite の順
  • height アニメーションは「Layout」再計算→「Paint」→「Composite」と全工程が発生
  • レイアウトプロパティ(例:height, width, top, left)はアニメーションコストが非常に高い
  • ペイントプロパティ(例:SVGのfillやstroke)はレイアウトなしでPaintとComposite
  • 最も安価なプロパティは Compositeプロパティ (例:transform, opacity)
    • これらはLayoutもPaintも発生せずCompositeのみ

transformによるアニメーション最適化

  • heightアニメーションtransform: scaleY() に置換することでパフォーマンス大幅改善
  • ただし、 scaleY は角丸が歪むなど見た目の問題が発生
  • 解決策として、 2つの矩形を上下から移動 させて1本のバーに見せる手法を採用
    • 各端に1つずつ矩形を配置し、それぞれ逆方向にtranslate
  • これにより、 Layout・Paintフェーズをスキップ し、Compositeのみで動作
  • 最適化後、CPU・GPU使用率が大幅減少(CPU 6%、GPU 1%未満)

パフォーマンス改善のポイントと今後

  • レンダリングパイプライン理解適切なCSSプロパティ選択 が重要
  • Chromeの about://tracing ツールでさらに詳細なパフォーマンス分析が可能
  • Granolaの最適化版ビジュアライザーは実際にアプリで体験可能
  • パフォーマンス最適化に興味がある方は採用情報も要チェック

Hackerたちの意見

簡単なアニメーションGIFのパフォーマンスが、合成レイヤーの変換の代わりにどうなるのか気になるなぁ。

GIFは歴史的に見ても、表示の観点からあまり最適化されてなかったから(中程度にアクティブなTumblrダッシュボードを持ってる人なら誰でもわかるよね)、もし悪化してたとしても驚かないよ。

もし実際にコントロールしたいなら、SVGやそれに基づくアニメーションがいいかも。

バーが実際のデータに関連してなくて、ただのプリセットアニメーションなら、GIFは一度のキャッシュダウンロードのために追加のファイルサイズが必要になるだけだよ。もしバーがリアルタイムのデータに合わせてアニメーションする必要があるなら、GIFはそのニーズには合わないと思う。

個人的には、リソースを消費するアニメーションやぼやけたGIF/canvasにはイライラするな。InfisicalはUIのアイコンに後者(canvas)を使ってるけど、あんまり好きじゃない。シャープで静的なアイコンの方がいいな。

ちょっとバカな質問だけど、親のdivを絶対位置にしたら合成の問題が解決するかな?

もうそうだったよ。これの全体のポイントは、高さの変更がレンダリングパイプラインの高コストなフェーズの一部だってことなんだ。

驚くのは、M2 Macのリソースがウェブサイトのレンダリングにこんなに使われることだよね。全てをゼロからレンダリングしても、数十年前のビデオゲームに比べたらグラフィックコンテンツはほとんどないのに、あっちの方が古いハードウェアでフレームを半分の時間で簡単にレンダリングしてたのに。

まあ、生のグラフィックコンテンツは簡単な部分だよね。ライブレイアウトツリーは再描画や合成が必要で、アクセシビリティツリーみたいな任意のレイヤーと交差しなきゃいけないから、N:Nスタイルの計算とは全然違うアーキテクチャなんだ。DOMをキャンバスで置き換えるっていうアイデアを試してる人たちの例を考えてみて。基本的なテキスト選択すらうまくいかなくなるからね。

アクティビティモニターのパーセンテージは、コアごとに100%だから、10コアのCPUだと最大で1000%の使用率になるよ。だから60%っていうのは、全体のCPUじゃなくて、1つのコアの60%ってことね。

この場合、以下のリンクも見てみるといいよ:https://developer.mozilla.org/en-US/docs/Web/CSS/contain https://developer.mozilla.org/en-US/docs/Web/CSS/will-change アニメーションのレイアウトプロパティに影響を与えるヒントをブラウザに提供できるかもしれないから。

これについてコメントしようとしてたんだけど、先に言われちゃった!親要素にcontain: strict;を設定すれば十分だったと思うよ。

Hacker Newsで議論の続きを見る