perf(tracklist): drop now-playing pulse + EQ-bar animations (#488)

* perf(tracklist): replace animated EQ-bar + active-row pulse with static icon

The currently-playing track in any tracklist (AlbumDetail, ArtistDetail,
PlaylistDetail, Favorites, RandomMix) was rendered with two animations
on top of an already-busy DOM:

- `.track-row.active` ran `track-pulse` 3s opacity 1 → 0.6 → 1 infinite
  on the entire row subtree (title button, several Lucide icons, star
  rating SVGs, hover affordances). Opacity is a compositor property, but
  on WebKitGTK without compositing — Linux + NVIDIA proprietary +
  WEBKIT_DISABLE_COMPOSITING_MODE=1 — every animated row falls back to a
  full software repaint of the subtree per frame.
- The "now playing" indicator in the track-number cell was three
  `<span class="eq-bar">` siblings with `transform: scaleY()` keyframes
  (`eq-bounce`), each on its own delay/duration. Same composite story:
  three software-repainted layers per frame, every frame.

On AlbumDetail (long tracklist + cover-art header background + the
already-running WaveformSeek progress rAF in the player bar) the
combined cost held the WebProcess at ~80 % CPU with 1.2 GB RSS for the
entire duration of playback. CPU dropped immediately on pause/stop;
on Composers / Settings (no track rows) the symptom never appeared.
Profiler confirmed continuous Layout & Rendering work synchronised with
isPlaying, not the canvas itself.

Replace both animations with static visuals:

- `.track-row.active` keeps the `--accent-dim` background, drops the
  pulse animation entirely.
- The "now playing" indicator becomes a single Lucide `AudioLines` icon
  (four vertical bars of different heights — reads as an EQ icon, no
  animation, one SVG per active row instead of three animated spans).
  `.eq-bars` className now only sets the accent colour.

Cleanup: dead `@keyframes track-pulse`, `@keyframes eq-bounce`,
`.eq-bars.paused` rule, plus a duplicate `.eq-bar` block in theme.css
(with a `wave` keyframe that was being shadowed by components.css and
had no other consumers).

No behaviour change beyond removing the animation; the row is still
visibly the active one and the icon still marks the playing track.

* docs: CHANGELOG entry for tracklist animation perf fix (PR #488)
This commit is contained in:
Frank Stellmacher
2026-05-07 01:57:14 +02:00
committed by GitHub
parent 59744601d4
commit 5b37ab70f1
8 changed files with 26 additions and 112 deletions
+3 -3
View File
@@ -3,7 +3,7 @@ import { getGenres, SubsonicSong, SubsonicGenre, star, unstar } from '../api/sub
import { usePlayerStore, songToTrack } from '../store/playerStore';
import { usePreviewStore } from '../store/previewStore';
import { useAuthStore, RANDOM_MIX_SIZE_OPTIONS } from '../store/authStore';
import { Play, RefreshCw, ChevronDown, ChevronRight, ChevronUp, Heart, Square } from 'lucide-react';
import { Play, RefreshCw, ChevronDown, ChevronRight, ChevronUp, Heart, Square, AudioLines } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useDragDrop } from '../contexts/DragDropContext';
import { useIsMobile } from '../hooks/useIsMobile';
@@ -491,7 +491,7 @@ export default function RandomMix() {
>
<div className={`track-num${isCurrentTrack ? ' track-num-active' : ''}`}>
{isCurrentTrack && isPlaying ? (
<span className="track-num-eq"><div className="eq-bars"><span className="eq-bar" /><span className="eq-bar" /><span className="eq-bar" /></div></span>
<span className="track-num-eq"><AudioLines className="eq-bars" size={14} /></span>
) : (
<span className="track-num-number">{idx + 1}</span>
)}
@@ -623,7 +623,7 @@ export default function RandomMix() {
>
<div className={`track-num${isCurrentTrack ? ' track-num-active' : ''}`}>
{isCurrentTrack && isPlaying ? (
<span className="track-num-eq"><div className="eq-bars"><span className="eq-bar" /><span className="eq-bar" /><span className="eq-bar" /></div></span>
<span className="track-num-eq"><AudioLines className="eq-bars" size={14} /></span>
) : (
<span className="track-num-number">{idx + 1}</span>
)}