From b0fb1b200d6bdaf273f7d95e6e6cb1ecf6c72f87 Mon Sep 17 00:00:00 2001 From: Honey Date: Mon, 18 May 2026 23:10:03 +0200 Subject: [PATCH] fix: smooth NP swipe-open animation; live playing highlight in song lists Swipe animation: - _resetDrag(true) now reads the overlay's current drag position and starts the CSS transition from there instead of resetting to translateY(100%) first. That reset was causing the visible stutter (overlay jumped off-screen then slid back up). Now the overlay continues seamlessly from wherever the finger released to translateY(0). Playing highlight: - makeSongItem: adds data-song-id attribute to every .song-item element. - updatePlayerUI: calls _updatePlayingHighlights() after every track change. This toggles .playing on all visible song items in any open list/playlist/ favorites view so the highlighted row always matches what's actually playing, without requiring the user to navigate away and back. Co-Authored-By: Claude Sonnet 4.6 --- src/components.js | 1 + src/main.js | 25 +++++++++++++++++++++++-- src/player.js | 8 ++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/components.js b/src/components.js index 755cc94..9524f22 100644 --- a/src/components.js +++ b/src/components.js @@ -31,6 +31,7 @@ export function makeSongItem(s, idx, list, { noArtistLink = false, noAlbumLink = const el = document.createElement('div'); if(s.starred && !s._starred) s._starred = true; el.className = 'song-item' + (S.queue[S.qIdx]?.id === s.id ? ' playing' : ''); + el.dataset.songId = s.id; const cv = coverUrl(s.coverArt, 128); const artistParts = (s.artist || '').split(/\s*(?:\/|•|,)\s*/).filter(Boolean); const artistHtml = artistParts.length diff --git a/src/main.js b/src/main.js index bb9a18b..c1f67c6 100644 --- a/src/main.js +++ b/src/main.js @@ -221,8 +221,29 @@ window.addEventListener('load', () => { function _resetDrag(open) { playerBar.classList.remove('dragging'); playerBar.style.transform = ''; - if(npOverlay) { npOverlay.style.transition = ''; npOverlay.style.transform = ''; } - if(open) openNowPlaying(); + + if (!npOverlay) { if(open) openNowPlaying(); _sy=0; _dragging=false; return; } + + if (open) { + // Continue animating from the current drag position instead of + // resetting to translateY(100%) first (which caused the stutter). + const m = npOverlay.style.transform.match(/translateY\(([^p]+)px\)/); + const curY = m ? parseFloat(m[1]) : window.innerHeight; + npOverlay.style.transition = 'none'; + npOverlay.style.transform = `translateY(${curY}px)`; + void npOverlay.offsetHeight; // flush layout so curY is the transition start + npOverlay.style.transition = ''; + npOverlay.style.transform = 'translateY(0)'; + npOverlay.addEventListener('transitionend', () => { + npOverlay.style.transform = ''; + npOverlay.style.transition = ''; + }, { once: true }); + openNowPlaying(); + } else { + npOverlay.style.transition = ''; + npOverlay.style.transform = ''; + } + _sy=0; _dragging=false; } diff --git a/src/player.js b/src/player.js index d957985..d5d0aae 100644 --- a/src/player.js +++ b/src/player.js @@ -356,6 +356,13 @@ export function playCurrent() { }); } +function _updatePlayingHighlights() { + const id = String(S.queue[S.qIdx]?.id ?? ''); + document.querySelectorAll('.song-item[data-song-id]').forEach(el => { + el.classList.toggle('playing', el.dataset.songId === id); + }); +} + export function updatePlayerUI(s, _skipMediaSession = false) { document.getElementById('player-title').textContent = s.title; const artistParts = (s.artist || '').split(/\s*(?:\/|•|,)\s*/).filter(Boolean); @@ -387,6 +394,7 @@ export function updatePlayerUI(s, _skipMediaSession = false) { })); } catch {} _syncNpDirect(s); + _updatePlayingHighlights(); // Preload the hi-res NP cover now so opening the overlay is instant const _npCv = coverUrl(s.coverArt, 1024); if (_npCv && _npCv !== _npBgLastUrl) { const _pre = new Image(); _pre.src = _npCv; }