Animated chat backgrounds animate background-position, which repaints the whole chat area every frame (45–70% main-thread busy while idle in testing) #337

Closed
opened 2026-10-06 00:11:44 -04:00 by jared · 1 comment
Owner

All five animated chat backgrounds (P5-4) are CSS keyframes on background-position:

animRain_rainFall            [backgroundPositionX, backgroundPositionY]
animStars_starDrift          [backgroundPositionX, backgroundPositionY]
animPulse_gridPulse          [backgroundPositionX, backgroundPositionY]
animAurora_auroraFlow        [backgroundPositionX, backgroundPositionY]
animFireflies_firefliesDrift [backgroundPositionX, backgroundPositionY]

background-position can't run on the compositor, so every frame repaints the full chat-area background on the main thread. Main-thread busy time measured over 20 s with the app idle in a room (Chromium, CDP TaskDuration):

background main thread busy style recalcs/s
none 0.6 % 4
anim-rain 44.8 % 51
anim-pulse 56.7 % 42
anim-aurora 58.3 % 40
anim-stars 68.0 % 39
anim-fireflies 69.8 % 37

This was headless Chromium with software rasterisation, so absolute numbers are inflated; GPU raster makes it cheaper but doesn't remove the per-frame paint. For comparison, the Deep Space seasonal overlay costs 3.6 %. Pause animations and prefers-reduced-motion both bring it back to 0.6 % ✔. On laptops the defaults mean constant fan and battery use whenever a room is open. In the desktop app, the renderer is kept running while the window sits in the tray (P5-42), so it's worth checking that the animation stops when the window is hidden.

Fix:

  • Move each pattern onto a pseudo-element or extra layer and animate transform: translate() (with will-change: transform; the layer is oversized and tiled). That runs on the compositor at near-zero main-thread cost.
  • Optionally drop to ~20–30 fps (steps()) for the slow drifts.
  • Pause while the window is hidden or unfocused for a long time (visibilitychange, plus Tauri's hide-to-tray).

Found in the October 2026 client audit (Playwright against a local Synapse + LiveKit stack, plus code reading). Code refs are at cinny 30e2dbcd, cinny-desktop 8f67ec7, element-call 3b0050e8.

All five animated chat backgrounds (P5-4) are CSS keyframes on **`background-position`**: ``` animRain_rainFall [backgroundPositionX, backgroundPositionY] animStars_starDrift [backgroundPositionX, backgroundPositionY] animPulse_gridPulse [backgroundPositionX, backgroundPositionY] animAurora_auroraFlow [backgroundPositionX, backgroundPositionY] animFireflies_firefliesDrift [backgroundPositionX, backgroundPositionY] ``` `background-position` can't run on the compositor, so every frame repaints the full chat-area background on the main thread. Main-thread busy time measured over 20 s with the app idle in a room (Chromium, CDP `TaskDuration`): | background | main thread busy | style recalcs/s | |---|---|---| | none | 0.6 % | 4 | | anim-rain | 44.8 % | 51 | | anim-pulse | 56.7 % | 42 | | anim-aurora | 58.3 % | 40 | | anim-stars | 68.0 % | 39 | | anim-fireflies | 69.8 % | 37 | This was headless Chromium with software rasterisation, so absolute numbers are inflated; GPU raster makes it cheaper but doesn't remove the per-frame paint. For comparison, the Deep Space seasonal overlay costs 3.6 %. **Pause animations** and `prefers-reduced-motion` both bring it back to 0.6 % ✔. On laptops the defaults mean constant fan and battery use whenever a room is open. In the desktop app, the renderer is kept running while the window sits in the tray (P5-42), so it's worth checking that the animation stops when the window is hidden. **Fix:** - Move each pattern onto a pseudo-element or extra layer and animate **`transform: translate()`** (with `will-change: transform`; the layer is oversized and tiled). That runs on the compositor at near-zero main-thread cost. - Optionally drop to ~20–30 fps (`steps()`) for the slow drifts. - Pause while the window is hidden or unfocused for a long time (`visibilitychange`, plus Tauri's hide-to-tray). --- _Found in the October 2026 client audit (Playwright against a local Synapse + LiveKit stack, plus code reading). Code refs are at cinny `30e2dbcd`, cinny-desktop `8f67ec7`, element-call `3b0050e8`._
jared added the priority: lowperformancearea: appearance labels 2026-10-06 00:11:44 -04:00
Author
Owner

Checked the open question about the tray. With the Linux nightly 4.12.377 hidden to tray through its own resolve_close_request('tray'), the page gets visibilitychange → hidden (document.hidden: true). requestAnimationFrame drops from 56 frames/3 s to 0 and 100 ms intervals are throttled (8 ticks in 3 s), so painting stops while hidden ✔. The cost in this issue applies while the window is visible, including when it's behind other windows or on another monitor, which is most of a workday.

Checked the open question about the tray. With the Linux nightly 4.12.377 hidden to tray through its own `resolve_close_request('tray')`, the page gets `visibilitychange → hidden` (`document.hidden: true`). `requestAnimationFrame` drops from 56 frames/3 s to **0** and 100 ms intervals are throttled (8 ticks in 3 s), so painting stops while hidden ✔. The cost in this issue applies while the window is **visible**, including when it's behind other windows or on another monitor, which is most of a workday.
jared closed this issue 2026-10-16 20:52:35 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: LotusGuild/cinny#337