Skip to content

perf: eliminate always-on animations causing constant CPU/GPU load when idle#358

Open
psychosomat wants to merge 1 commit into
JOYCEQL:mainfrom
psychosomat:perf/remove-always-on-animations
Open

perf: eliminate always-on animations causing constant CPU/GPU load when idle#358
psychosomat wants to merge 1 commit into
JOYCEQL:mainfrom
psychosomat:perf/remove-always-on-animations

Conversation

@psychosomat

Copy link
Copy Markdown

Problem

While using the app (especially the workbench page), CPU/GPU usage stays high even when the user is completely idle, causing fans to spin up and laptops to heat up. Profiling showed the cause is a set of decorative animations that run in infinite loops on every frame, regardless of user activity:

  1. FAQ trigger button (FAQDialog.tsx) — the worst offender. The button container runs an infinite bounce animation while containing backdrop-blur-md and blur-xl layers. Because the blurred element moves every frame, the browser must recompute the blur filter continuously. On top of that, the SVG inside runs 4 infinite spin animations, 2 infinite pulse animations, and 3 SMIL <animate> loops simultaneously. SVG inner-element transforms are not compositor-accelerated, so these repaint on the CPU every frame.

  2. MagicThread in PreviewDock.tsx — an infinite framer-motion animation of the top property. top is a layout property, so this triggers a JS callback + style recalculation + layout on every frame (~60 fps), forever, on a page that is otherwise static.

  3. Backup status badge in EditorHeader.tsx — an infinite JS-driven framer-motion scale pulse, keeping a requestAnimationFrame loop alive at all times when backup is not configured.

Changes

  • FAQDialog.tsx: removed the always-on bounce/pulse on the trigger; all decorative SVG spin/pulse animations are now group-hover: only, so the attention-grabbing motion still plays on hover but costs nothing when idle. Replaced the SMIL sparkle loops with static opacity (hover restores the pulse). Also added aria-hidden to the decorative SVG (fixes a Biome a11y error).
  • PreviewDock.tsx: replaced the framer-motion top animation with a CSS keyframe (thread-travel) animating transform/opacity — these run entirely on the compositor thread with no JS involvement and no layout work. The travel distance is passed via a CSS custom property, preserving the exact same visual.
  • globals.css: added the thread-travel keyframes.
  • EditorHeader.tsx: replaced the infinite framer-motion scale loop with the CSS animate-pulse utility (opacity-only, compositor-friendly), keeping the visual cue that backup is not configured.

Rationale

  • Decorative motion should be either hover-triggered or compositor-driven (transform/opacity). Infinite JS-driven animations and animations of layout properties (top) keep the main thread busy 100% of the time for purely cosmetic effects.
  • Animating an element that contains backdrop-blur/blur is particularly expensive, as the blur must be re-resolved every frame.
  • No functional behavior is affected; all visuals are preserved (hover state) or visually equivalent (thread pulse, backup badge pulse).

Testing

  • npx tsc --noEmit passes for all touched files.
  • Verified the workbench visually: the dock thread pulse and FAQ button render identically; FAQ button animations play on hover.
  • CPU usage on an idle workbench page drops to near zero (previously constant style/layout/paint activity in the Performance panel).

- FAQDialog: remove infinite bounce/pulse on the trigger button and make
  decorative SVG spin/pulse animations hover-only. The bouncing container
  includes backdrop-blur and blur-xl layers, forcing expensive blur
  recomposition on every frame even when idle.
- PreviewDock (MagicThread): replace the infinite framer-motion animation
  of 'top' (a layout property, JS-driven at 60fps) with a CSS keyframe
  animating transform/opacity, which runs entirely on the compositor.
- EditorHeader: replace the infinite JS-driven framer-motion scale pulse
  on the backup status icon with the CSS animate-pulse utility.
- globals.css: add the 'thread-travel' keyframes used by MagicThread.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant