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
+7 -63
View File
@@ -2007,24 +2007,10 @@ html[data-track-previews-randommix="off"] [data-preview-loc="randomMix"] .pl
.track-row.active {
background: var(--accent-dim);
animation: track-pulse 3s ease-in-out infinite;
}
.track-row.active:hover {
background: var(--accent-dim);
animation: none;
}
@keyframes track-pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.6;
}
}
.track-row>* {
@@ -2111,56 +2097,14 @@ html[data-track-previews-randommix="off"] [data-preview-loc="randomMix"] .pl
color: var(--accent);
}
/* Equalizer bars — shown for the currently playing track */
/* "Now playing" indicator Lucide AudioLines icon coloured with the active
* accent. Was an animated three-bar transform setup; the per-frame opacity +
* scaleY repaints fell back to software composite on WebKitGTK without
* compositing (NVIDIA proprietary + WEBKIT_DISABLE_COMPOSITING_MODE=1) and
* pegged the WebProcess at ~80% CPU on AlbumDetail with a long tracklist. */
.eq-bars {
display: flex;
align-items: flex-end;
justify-content: center;
gap: 2px;
width: 14px;
height: 13px;
}
.eq-bar {
flex: 1;
height: 100%;
background: var(--accent);
border-radius: 1px;
transform-origin: bottom;
transform: scaleY(0.25);
animation: eq-bounce 1.1s ease-in-out infinite;
}
.eq-bar:nth-child(1) {
animation-delay: 0s;
animation-duration: 1.0s;
}
.eq-bar:nth-child(2) {
animation-delay: 0.18s;
animation-duration: 1.3s;
}
.eq-bar:nth-child(3) {
animation-delay: 0.35s;
animation-duration: 0.9s;
}
/* Pause EQ bars animation when playback is paused */
.eq-bars.paused .eq-bar {
animation-play-state: paused;
}
@keyframes eq-bounce {
0%,
100% {
transform: scaleY(0.25);
}
50% {
transform: scaleY(1);
}
color: var(--accent);
display: block;
}
/* CD / Disc separator */
-37
View File
@@ -4075,18 +4075,6 @@ select.input.input:focus {
}
}
@keyframes wave {
0%,
100% {
transform: scaleY(0.4);
}
50% {
transform: scaleY(1);
}
}
.animate-fade-in {
animation: fadeIn var(--transition-slow) both;
}
@@ -6528,31 +6516,6 @@ input[type="range"]:hover::-webkit-slider-thumb {
--font-display: 'Unbounded Variable', sans-serif;
}
/* ─── Equalizer bars animation ─── */
.eq-bar {
display: inline-block;
width: 3px;
background: var(--accent);
border-radius: 2px;
transform-origin: bottom;
}
.eq-bar:nth-child(1) {
animation: wave 0.8s ease-in-out 0.0s infinite;
}
.eq-bar:nth-child(2) {
animation: wave 0.8s ease-in-out 0.15s infinite;
}
.eq-bar:nth-child(3) {
animation: wave 0.8s ease-in-out 0.3s infinite;
}
.eq-bar:nth-child(4) {
animation: wave 0.8s ease-in-out 0.45s infinite;
}
/*
Middle Earth Epic Fantasy / Cinematic
*/