feat: smooth progress bar scrubbing with touch drag support

- initProgressScrub() adds touchstart/move/end + mousedown drag to both
  the player bar and NP overlay progress bars
- CSS transition disabled during scrub so fill follows finger instantly
- updateProgress() skips DOM updates while _scrubbing is active so
  timeupdate events don't fight the drag position
- Works on both bars with a shared _initBarScrub helper

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-04-25 18:45:23 +02:00
1 parent 08d2f69720
commit 4b34bbc043
2 files changed
+39 -7

No files matched your search

+38 -6
View File
@@ -122,6 +122,7 @@ window.addEventListener('load',()=>{
previewAudio.addEventListener('ended',stopPreview);
audio.volume=0.8;
injectPWA();
initProgressScrub();
// Now Playing: click player-art to open
const pa=document.getElementById('player-art');
if(pa)pa.addEventListener('click', openNowPlaying);
@@ -1057,7 +1058,7 @@ function expandArtistBio(){
if(el&&window._artistBioFull)el.textContent=window._artistBioFull;
}
function updateProgress(){
if(!audio.duration)return;
if(!audio.duration||_scrubbing)return;
syncMediaSessionPosition();
const p=(audio.currentTime/audio.duration)*100;
document.getElementById('progress-fill').style.width=p+'%';
@@ -1074,6 +1075,41 @@ function updateProgress(){
}
}
function seekTo(e){const bar=document.getElementById('progress-bar');const rect=bar.getBoundingClientRect();audio.currentTime=((e.clientX-rect.left)/rect.width)*audio.duration}
let _scrubbing=false;
function initProgressScrub(){
_initBarScrub(
document.getElementById('progress-bar'),
document.getElementById('progress-fill'),
pos=>{
document.getElementById('time-cur').textContent=formatDur(pos*audio.duration);
}
);
_initBarScrub(
document.getElementById('np-bar'),
document.getElementById('np-fill'),
pos=>{
const el=document.getElementById('np-cur');
if(el)el.textContent=formatDur(pos*audio.duration);
}
);
}
function _initBarScrub(bar,fill,onMove){
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){_scrubbing=true;fill.style.transition='none';fill.style.width=(pos*100)+'%';onMove(pos);}
function moveScrub(pos){fill.style.width=(pos*100)+'%';onMove(pos);}
function endScrub(pos){_scrubbing=false;fill.style.transition='';if(audio.duration)audio.currentTime=pos*audio.duration;}
bar.addEventListener('touchstart',e=>{startScrub(getPos(e.touches[0].clientX));e.stopPropagation();},{passive:true});
bar.addEventListener('touchmove',e=>{if(_scrubbing)moveScrub(getPos(e.touches[0].clientX));},{passive:true});
bar.addEventListener('touchend',e=>{if(_scrubbing)endScrub(getPos(e.changedTouches[0].clientX));},{passive:true});
bar.addEventListener('mousedown',e=>{
startScrub(getPos(e.clientX));e.preventDefault();
const mm=e2=>{if(_scrubbing)moveScrub(getPos(e2.clientX));};
const mu=e2=>{if(_scrubbing)endScrub(getPos(e2.clientX));document.removeEventListener('mousemove',mm);document.removeEventListener('mouseup',mu);};
document.addEventListener('mousemove',mm);document.addEventListener('mouseup',mu);
});
}
function setVolume(v){audio.volume=v/100;previewAudio.volume=v/100}
// ─── STAR ───
@@ -1610,11 +1646,7 @@ function npToggleStar() {
.catch(e => toast('Fehler: ' + e.message));
}
function npSeek(e) {
const bar = document.getElementById('np-bar');
const rect = bar.getBoundingClientRect();
audio.currentTime = ((e.clientX - rect.left) / rect.width) * audio.duration;
}
function npSeek(e){}
let npQueueMode = false;
function switchNpTab(tab) {