fix: bottom bar no touch seek; lyrics seek on Android; smoother NP animation

- initProgressScrub: pass noTouch=true for the bottom mini-bar so touch
  events are never registered on it; only mouse events remain (desktop-only).
  The now-playing scrubber is unchanged and keeps full touch support.
- Export seekToMs(ms) from player.js; use it in the lyrics click handler
  instead of S.audio.currentTime which has no effect when HAS_BRIDGE
  (native ExoPlayer handles playback, not the HTML audio element).
- styles.css: add will-change:transform to .player and .np-overlay to keep
  them on GPU compositor layers during the swipe gesture; tweak NP open
  transition to cubic-bezier(.4,0,.2,1) for a snappier feel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-05-18 22:42:58 +02:00
1 parent 9d8a5a22bb
commit 4856f0ca57
3 files changed
+40 -25

No files matched your search

+2 -2
View File
@@ -2,7 +2,7 @@ import { S } from './state.js';
import { esc } from './utils.js';
import { coverUrl } from './api.js';
// Circular imports — safe (only used inside function bodies)
import { playCurrent, _syncNpDirect, dlSongLocal, npToggleStar, _syncOverlayState, isPlaying } from './player.js';
import { playCurrent, _syncNpDirect, dlSongLocal, npToggleStar, _syncOverlayState, isPlaying, seekToMs } from './player.js';
import { navigate } from './views.js';
import { openPlModal, showSongInfo } from './ui.js';
@@ -116,7 +116,7 @@ function _renderSyncedLines(content, lines) {
const el = document.createElement('div');
el.className = 'lyrics-line';
el.textContent = line.value;
el.onclick = () => { S.audio.currentTime = line.start / 1000; };
el.onclick = () => { seekToMs(line.start); };
content.appendChild(el);
return { start: line.start, el };
});
+35 -21
View File
@@ -656,10 +656,12 @@ export function seekTo(e) {
}
export function initProgressScrub() {
// Bottom mini-bar: mouse-only (touch disabled — too easy to accidentally seek on mobile).
_initBarScrub(
document.getElementById('progress-bar'),
document.getElementById('progress-fill'),
pos => { document.getElementById('time-cur').textContent = formatDur(pos * _dur()); }
pos => { document.getElementById('time-cur').textContent = formatDur(pos * _dur()); },
/* noTouch= */ true
);
_initBarScrub(
document.getElementById('np-bar'),
@@ -668,7 +670,17 @@ export function initProgressScrub() {
);
}
function _initBarScrub(bar, fill, onMove) {
// Export for lyrics / other callers that need to seek by ms.
export function seekToMs(ms) {
if (HAS_BRIDGE) {
_setSkipping();
_nsBridge('seekTo', Math.round(ms));
} else {
S.audio.currentTime = ms / 1000;
}
}
function _initBarScrub(bar, fill, onMove, noTouch = false) {
if(!bar || !fill) return;
function getPos(clientX) { const r = bar.getBoundingClientRect(); return Math.max(0, Math.min(1, (clientX-r.left)/r.width)); }
function startScrub(pos) { S._scrubbing=true; fill.style.transition='none'; fill.style.width=(pos*100)+'%'; onMove(pos); }
@@ -691,25 +703,27 @@ function _initBarScrub(bar, fill, onMove) {
fill.style.transition = '';
}
}
// Touch: require ≥8px horizontal drag before committing to scrub.
// A plain tap cancels cleanly without seeking.
let _touchStartX = 0, _touchMoved = false;
bar.addEventListener('touchstart', e => {
_touchStartX = e.touches[0].clientX;
_touchMoved = false;
e.stopPropagation();
e.preventDefault(); // block synthetic mousedown/click that would trigger scrub on plain tap
});
bar.addEventListener('touchmove', e => {
const dx = Math.abs(e.touches[0].clientX - _touchStartX);
if (!_touchMoved && dx >= 8) { _touchMoved = true; startScrub(getPos(e.touches[0].clientX)); }
if (_touchMoved && S._scrubbing) moveScrub(getPos(e.touches[0].clientX));
}, {passive:true});
bar.addEventListener('touchend', e => {
if (_touchMoved && S._scrubbing) endScrub(getPos(e.changedTouches[0].clientX));
else S._scrubbing = false;
_touchMoved = false;
}, {passive:true});
if (!noTouch) {
// Touch: require ≥8px horizontal drag before committing to scrub.
// A plain tap cancels cleanly without seeking.
let _touchStartX = 0, _touchMoved = false;
bar.addEventListener('touchstart', e => {
_touchStartX = e.touches[0].clientX;
_touchMoved = false;
e.stopPropagation();
e.preventDefault(); // block synthetic mousedown/click that would trigger scrub on plain tap
});
bar.addEventListener('touchmove', e => {
const dx = Math.abs(e.touches[0].clientX - _touchStartX);
if (!_touchMoved && dx >= 8) { _touchMoved = true; startScrub(getPos(e.touches[0].clientX)); }
if (_touchMoved && S._scrubbing) moveScrub(getPos(e.touches[0].clientX));
}, {passive:true});
bar.addEventListener('touchend', e => {
if (_touchMoved && S._scrubbing) endScrub(getPos(e.changedTouches[0].clientX));
else S._scrubbing = false;
_touchMoved = false;
}, {passive:true});
}
bar.addEventListener('mousedown', e => {
startScrub(getPos(e.clientX)); e.preventDefault();
const mm = e2 => { if(S._scrubbing) moveScrub(getPos(e2.clientX)); };
+3 -2
View File
@@ -223,7 +223,7 @@ button:focus{outline:none}
.btn-outline.blue:hover{background:var(--blue-dim)}
/* ── PLAYER ── */
.player{position:fixed;bottom:0;left:0;right:0;height:var(--player-h);background:rgba(8,8,20,.93);backdrop-filter:blur(28px);border-top:1px solid var(--border2);display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:0;padding:0 20px;z-index:100}
.player{position:fixed;bottom:0;left:0;right:0;height:var(--player-h);background:rgba(8,8,20,.93);backdrop-filter:blur(28px);border-top:1px solid var(--border2);display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:0;padding:0 20px;z-index:100;will-change:transform}
.player-drag-handle{position:absolute;top:5px;left:50%;transform:translateX(-50%);width:32px;height:3px;border-radius:2px;background:rgba(255,255,255,.18);transition:background .2s,width .2s;pointer-events:none}
.player.dragging .player-drag-handle{background:rgba(255,255,255,.45);width:44px}
.player-track{min-width:0;display:flex;align-items:center;gap:10px;overflow:visible}
@@ -521,8 +521,9 @@ button:focus{outline:none}
display:flex;flex-direction:column;
background:var(--bg);
transform:translateY(100%);
transition:transform .4s cubic-bezier(.32,.72,0,1);
transition:transform .35s cubic-bezier(.4,0,.2,1);
overflow:hidden;
will-change:transform;
}
.np-overlay.open{transform:translateY(0)}