improve: scroll to current lyric in real time while adjusting progress

This commit is contained in:
alikia2x 2024-07-12 14:39:45 +08:00
parent 00549a504c
commit e1aa87ece0
4 changed files with 105 additions and 50 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "aquavox", "name": "aquavox",
"version": "1.10.1", "version": "1.12.4",
"private": false, "private": false,
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",

View File

@ -17,6 +17,9 @@
let nextUpdate = -1; let nextUpdate = -1;
let lastAdjustProgress = 0; let lastAdjustProgress = 0;
let localProgress = 0; let localProgress = 0;
let lastScroll = 0;
let scrolling = false;
let scriptScrolling = false;
let refs: HTMLParagraphElement[] = []; let refs: HTMLParagraphElement[] = [];
let _refs: any[] = []; let _refs: any[] = [];
@ -30,8 +33,19 @@
else return 'previous-lyric'; else return 'previous-lyric';
} }
// scroll to correspoding lyric while adjusting progress
$: { $: {
if (lyricsContainer && originalLyrics && originalLyrics.scripts) { if ($userAdjustingProgress == true) {
const currentLyric = refs[getLyricIndex(progress)];
scrollToLyric(currentLyric);
}
}
$: {
(() => {
if (lyricsContainer === undefined || originalLyrics.scripts === undefined) {
return;
}
const scripts = originalLyrics.scripts; const scripts = originalLyrics.scripts;
currentPositionIndex = getLyricIndex(progress); currentPositionIndex = getLyricIndex(progress);
const cl = scripts[currentPositionIndex]; const cl = scripts[currentPositionIndex];
@ -42,7 +56,10 @@
currentLyricIndex = -1; currentLyricIndex = -1;
nextUpdate = cl.start; nextUpdate = cl.start;
} }
if ($userAdjustingProgress === false) { const currentLyric = refs[currentPositionIndex];
if ($userAdjustingProgress === true || scrolling || currentLyric.getBoundingClientRect().top < 0) {
return;
}
for (let i = 0; i < scripts.length; i++) { for (let i = 0; i < scripts.length; i++) {
const offset = Math.abs(i - currentPositionIndex); const offset = Math.abs(i - currentPositionIndex);
const blurRadius = Math.min(offset * 1.5, 16); const blurRadius = Math.min(offset * 1.5, 16);
@ -50,15 +67,14 @@
refs[i].style.filter = `blur(${blurRadius}px)`; refs[i].style.filter = `blur(${blurRadius}px)`;
} }
} }
} })();
}
} }
function sleep(ms: number) { function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
} }
async function a(h: number) { async function moveToNextLine(h: number) {
let pos = currentPositionIndex + 2; let pos = currentPositionIndex + 2;
for (let i = currentPositionIndex + 2; i < refs.length; i++) { for (let i = currentPositionIndex + 2; i < refs.length; i++) {
const lyric = refs[i]; const lyric = refs[i];
@ -96,12 +112,20 @@
for (let i = 0; i < refs.length; i++) { for (let i = 0; i < refs.length; i++) {
refs[i].style.transform = `translateY(0px)`; refs[i].style.transform = `translateY(0px)`;
} }
scriptScrolling = true;
lyricsContainer.scrollTop += h; lyricsContainer.scrollTop += h;
setTimeout(() => {
scriptScrolling = false;
}, 500);
} }
async function b(currentLyric: HTMLParagraphElement) { async function scrollToLyric(currentLyric: HTMLParagraphElement) {
if (!originalLyrics || !originalLyrics.scripts || !lyricsContainer) return; if (!originalLyrics || !originalLyrics.scripts || !lyricsContainer) return;
scriptScrolling = true;
lyricsContainer.scrollTop += currentLyric.getBoundingClientRect().top - 144; lyricsContainer.scrollTop += currentLyric.getBoundingClientRect().top - 144;
setTimeout(() => {
scriptScrolling = false;
}, 500);
} }
userAdjustingProgress.subscribe((v) => { userAdjustingProgress.subscribe((v) => {
@ -126,9 +150,10 @@
// progressBarRaw is used to detect progress changes at system-level (not in AquaVox) // progressBarRaw is used to detect progress changes at system-level (not in AquaVox)
progressBarRaw.subscribe((progress: number) => { progressBarRaw.subscribe((progress: number) => {
if ($userAdjustingProgress === false && getLyricIndex) { if ($userAdjustingProgress === false && getLyricIndex) {
// prevent calling too frequent
if (Math.abs(localProgress - progress) > 0.6) { if (Math.abs(localProgress - progress) > 0.6) {
const currentLyric = refs[getLyricIndex(progress)]; const currentLyric = refs[getLyricIndex(progress)];
b(currentLyric); scrollToLyric(currentLyric);
} }
localProgress = progress; localProgress = progress;
} }
@ -141,21 +166,52 @@
} }
}); });
function scrollHandler() {
scrolling = !scriptScrolling;
if (scrolling && originalLyrics.scripts) {
lastScroll = new Date().getTime();
for (let i = 0; i < originalLyrics.scripts.length; i++) {
if (refs[i]) {
refs[i].style.filter = 'blur(0px)';
}
}
}
setTimeout(() => {
if (new Date().getTime() - lastScroll > 5000) {
scrolling = false;
}
}, 5500);
}
$: { $: {
(() => {
if ($userAdjustingProgress) { if ($userAdjustingProgress) {
nextUpdate = progress; nextUpdate = progress;
} else { return;
if (nextUpdate - progress < 0.05) { }
if (nextUpdate - progress >= 0.05) {
return;
}
if ( if (
currentPositionIndex >= 0 && currentPositionIndex < 0 ||
currentPositionIndex !== currentAnimationIndex && currentPositionIndex === currentAnimationIndex ||
currentPositionIndex !== lastAdjustProgress currentPositionIndex === lastAdjustProgress ||
scrolling
) { ) {
return;
}
const currentLyric = refs[currentPositionIndex];
if (originalLyrics.scripts && currentLyric.getBoundingClientRect().top < 0) {
return;
}
const offsetHeight = const offsetHeight =
refs[currentPositionIndex].getBoundingClientRect().height + refs[currentPositionIndex].getBoundingClientRect().height +
refs[currentPositionIndex].getBoundingClientRect().top - refs[currentPositionIndex].getBoundingClientRect().top -
144; 144;
const currentLyric = refs[currentPositionIndex];
currentLyric.style.transition = currentLyric.style.transition =
'transform .6s cubic-bezier(.28,.01,.29,.99), filter 200ms ease, opacity 200ms ease, font-size 200ms ease, scale 250ms ease'; 'transform .6s cubic-bezier(.28,.01,.29,.99), filter 200ms ease, opacity 200ms ease, font-size 200ms ease, scale 250ms ease';
currentLyric.style.transform = `translateY(${-offsetHeight}px)`; currentLyric.style.transform = `translateY(${-offsetHeight}px)`;
@ -171,12 +227,10 @@
nextLyric.style.transition = nextLyric.style.transition =
'transform .6s cubic-bezier(.28,.01,.29,.99), filter 200ms ease, opacity 200ms ease, font-size 200ms ease, scale 250ms ease'; 'transform .6s cubic-bezier(.28,.01,.29,.99), filter 200ms ease, opacity 200ms ease, font-size 200ms ease, scale 250ms ease';
nextLyric.style.transform = `translateY(${-offsetHeight}px)`; nextLyric.style.transform = `translateY(${-offsetHeight}px)`;
a(offsetHeight); moveToNextLine(offsetHeight);
} }
currentAnimationIndex = currentPositionIndex; currentAnimationIndex = currentPositionIndex;
} })();
}
}
} }
</script> </script>
@ -185,6 +239,7 @@
class="absolute top-[6.5rem] md:top-36 xl:top-0 w-screen xl:w-[52vw] px-6 md:px-12 lg:px-[7.5rem] xl:left-[45vw] xl:px-[3vw] h-[calc(100vh-17rem)] xl:h-screen font-sans class="absolute top-[6.5rem] md:top-36 xl:top-0 w-screen xl:w-[52vw] px-6 md:px-12 lg:px-[7.5rem] xl:left-[45vw] xl:px-[3vw] h-[calc(100vh-17rem)] xl:h-screen font-sans
text-left no-scrollbar overflow-y-auto z-[1] pt-16 lyrics" text-left no-scrollbar overflow-y-auto z-[1] pt-16 lyrics"
bind:this={lyricsContainer} bind:this={lyricsContainer}
on:scroll={scrollHandler}
> >
{#if debugMode} {#if debugMode}
<p class="fixed top-6 right-20 font-mono text-sm"> <p class="fixed top-6 right-20 font-mono text-sm">

View File

@ -77,7 +77,7 @@
</ul> </ul>
</div> </div>
<p> <p>
AquaVox 1.10.1 · 早期公开预览 · 源代码参见 AquaVox 1.12.4 · 早期公开预览 · 源代码参见
<a href="https://github.com/alikia2x/aquavox">GitHub</a> <a href="https://github.com/alikia2x/aquavox">GitHub</a>
</p> </p>
<a href="/import">导入音乐</a> <br /> <a href="/import">导入音乐</a> <br />

View File

@ -72,13 +72,13 @@
> >
{song.singer.join(', ')} {song.singer.join(', ')}
</span> </span>
<div class="absolute right-2 bottom-2 text-right"> <div class="absolute right-2 bottom-2 text-right text-white" style="text-shadow: 0px 0px 4px rgba(65, 65, 65, .6);">
{#if song.duration} {#if song.duration}
<span style="text-shadow: 0px 0px 4px rgba(65, 65, 65, .6);">{formatDuration(song.duration)}</span> <span>{formatDuration(song.duration)}</span>
{/if} {/if}
<br /> <br />
{#if song.views} {#if song.views}
<span style="text-shadow: 0px 0px 4px rgba(65, 65, 65, .6);">{formatViews(song.views)}播放</span> <span>{formatViews(song.views)}播放</span>
{/if} {/if}
</div> </div>
</div> </div>