From e573c704974f9d834ac0a5779c82f84062b36ced Mon Sep 17 00:00:00 2001 From: alikia2x Date: Sun, 24 Nov 2024 03:58:26 +0800 Subject: [PATCH] improve: mobile UX fix: some UX bugs --- package.json | 2 +- .../components/import/fileSelector.svelte | 2 +- .../core/components/import/importIcon.svelte | 14 +--- .../core/components/interactiveBox.svelte | 70 ++++++++++++------ .../core/components/lyrics/lyricLine.svelte | 5 ++ .../core/components/lyrics/newLyrics.svelte | 18 +++-- packages/core/utils/formatText.ts | 3 + packages/core/utils/getCurrentTimestamp.ts | 4 + packages/web/src/app.html | 5 +- packages/web/src/routes/+page.svelte | 18 ++++- .../web/src/routes/play/[id]/+page.svelte | 66 ++++++++++------- packages/web/static/icon.png | Bin 9692 -> 0 bytes packages/web/static/manifest.json | 16 ---- packages/web/static/pause.svg | 12 +-- 14 files changed, 127 insertions(+), 108 deletions(-) create mode 100644 packages/core/utils/getCurrentTimestamp.ts delete mode 100644 packages/web/static/icon.png delete mode 100644 packages/web/static/manifest.json diff --git a/package.json b/package.json index 3abd572..ef334f6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aquavox", - "version": "2.9.3", + "version": "2.9.4", "private": false, "module": "index.ts", "type": "module", diff --git a/packages/core/components/import/fileSelector.svelte b/packages/core/components/import/fileSelector.svelte index f5eecd6..22cc9c9 100644 --- a/packages/core/components/import/fileSelector.svelte +++ b/packages/core/components/import/fileSelector.svelte @@ -35,7 +35,7 @@ {#if $fileItems.length > 0} {:else} - + {/if} diff --git a/packages/core/components/import/importIcon.svelte b/packages/core/components/import/importIcon.svelte index cf982d5..c97f9b3 100644 --- a/packages/core/components/import/importIcon.svelte +++ b/packages/core/components/import/importIcon.svelte @@ -1,15 +1,3 @@
- +
diff --git a/packages/core/components/interactiveBox.svelte b/packages/core/components/interactiveBox.svelte index 83fa1a7..c250b6f 100644 --- a/packages/core/components/interactiveBox.svelte +++ b/packages/core/components/interactiveBox.svelte @@ -4,6 +4,7 @@ import userAdjustingProgress from '../state/userAdjustingProgress'; import progressBarSlideValue from '../state/progressBarSlideValue'; import truncate from '../utils/truncate'; + import timestamp from '@core/utils/getCurrentTimestamp'; export let name: string; export let singer: string = ''; @@ -19,7 +20,7 @@ export let hasLyrics: boolean; export let showInteractiveBox: boolean; - export let setShowingInteractiveBox: Function; + export let showingInteractiveBoxUntil: Function; let progressBar: HTMLDivElement; let volumeBar: HTMLDivElement; @@ -29,12 +30,11 @@ let songInfoTopContent: HTMLSpanElement; let userAdjustingVolume = false; let lastTouchClientX = 0; + let mobileDeviceAdjustingProgress = false; - setTimeout(() => { - if (screen.width < 728) { - setShowingInteractiveBox(false); - } - }, 3000); + if (screen.width < 728) { + showingInteractiveBoxUntil(timestamp() + 3000); + } const mql = window.matchMedia('(max-width: 1280px)'); @@ -81,27 +81,44 @@ window.addEventListener("mousemove", (event) => { if ($userAdjustingProgress) { - adjustDisplayProgress(event.offsetX / progressBar.getBoundingClientRect().width); + const x = event.clientX; + const rec = progressBar.getBoundingClientRect(); + adjustDisplayProgress(truncate((x - rec.left) / rec.width,0,1)); } }); window.addEventListener("mouseup", (event) => { if ($userAdjustingProgress) { + const x = event.clientX; + const rec = progressBar.getBoundingClientRect(); userAdjustingProgress.set(false); - adjustProgress(event.offsetX / progressBar.getBoundingClientRect().width); + adjustProgress(truncate((x - rec.left) / rec.width,0,1)); } }); window.addEventListener("touchmove", (event) => { if ($userAdjustingProgress) { - adjustDisplayProgress((event.touches[0].clientX - progressBar.getBoundingClientRect().left) / progressBar.getBoundingClientRect().width); - lastTouchClientX = event.touches[0].clientX; + const x = event.touches[0].clientX; + const rec = progressBar.getBoundingClientRect(); + adjustDisplayProgress(truncate((x - rec.left) / rec.width,0,1)); + lastTouchClientX = x; } }); window.addEventListener("touchend", (event) => { if ($userAdjustingProgress) { - adjustProgress((lastTouchClientX - progressBar.getBoundingClientRect().left) / progressBar.getBoundingClientRect().width); + const x = lastTouchClientX; + const rec = progressBar.getBoundingClientRect(); + adjustProgress(truncate((x - rec.left) / rec.width,0,1)); userAdjustingProgress.set(false); + mobileDeviceAdjustingProgress = false; } }); + + userAdjustingProgress.subscribe(()=> { + showingInteractiveBoxUntil(timestamp() + 5000); + }); + + function handleClick() { + showingInteractiveBoxUntil(timestamp() + 5000); + } {#if showInfoTop} @@ -112,11 +129,11 @@ {/if}
@@ -132,12 +149,12 @@
{/if} -
{ - setShowingInteractiveBox(true); - setTimeout(() => { - setShowingInteractiveBox(false); - }, 5000); - }}>
+ + +
@@ -148,7 +165,7 @@ aria-valuemin="0" aria-valuenow={progress} bind:this={progressBar} - class="progress-bar shadow-md" + class="progress-bar shadow-md {mobileDeviceAdjustingProgress && '!h-[0.7rem]'}" on:keydown on:keyup on:click={(e) => { @@ -159,11 +176,12 @@ }} on:touchstart={() => { userAdjustingProgress.set(true); + mobileDeviceAdjustingProgress = true; }} role="slider" tabindex="0" > -
+
{formatDuration(duration)}
@@ -370,7 +388,7 @@ transition: 0.3s; } - .progress-bar:hover { + .progress-bar:active { height: 0.7rem; } @@ -384,7 +402,7 @@ transition: height 0.3s; } - .progress-bar:hover .bar { + .progress-bar:active .bar { height: 0.7rem; } @@ -410,5 +428,11 @@ .control-btn { transition: 0.1s } + .progress-bar:hover { + height: 0.7rem; + } + .progress-bar:hover .bar { + height: 0.7rem; + } } diff --git a/packages/core/components/lyrics/lyricLine.svelte b/packages/core/components/lyrics/lyricLine.svelte index 9556d2a..f81196a 100644 --- a/packages/core/components/lyrics/lyricLine.svelte +++ b/packages/core/components/lyrics/lyricLine.svelte @@ -134,6 +134,11 @@ }; + export const syncSpringWithDelta = (deltaY: number) => { + const target = positionY + deltaY; + springY!.setPosition(target); + } + export const getInfo = () => { return { x: positionX, diff --git a/packages/core/components/lyrics/newLyrics.svelte b/packages/core/components/lyrics/newLyrics.svelte index 365ab37..457ed72 100644 --- a/packages/core/components/lyrics/newLyrics.svelte +++ b/packages/core/components/lyrics/newLyrics.svelte @@ -116,6 +116,7 @@ scrolling = true; currentLyricComponent.stop(); currentLyricComponent.setY(currentY - deltaY); + currentLyricComponent.syncSpringWithDelta(deltaY); } scrolling = true; if (scrollingTimeout) clearTimeout(scrollingTimeout); @@ -250,23 +251,26 @@ {#if debugMode} - progress: {progress.toFixed(2)}, nextUpdate: {nextUpdate}, scrolling: {scrolling}, current: {currentLyricIndex}, uap: {$userAdjustingProgress} + class="text-white text-lg absolute z-50 px-2 py-0.5 m-2 rounded-3xl bg-white bg-opacity-20 backdrop-blur-lg + right-0 font-mono"> + progress: {progress.toFixed(2)}, nextUpdate: {nextUpdate}, scrolling: {scrolling}, current: {currentLyricIndex}, + uap: {$userAdjustingProgress} {/if} {#if originalLyrics && originalLyrics.scripts}
{#each lyricLines as lyric, i} - + {/each}
{/if} diff --git a/packages/core/utils/formatText.ts b/packages/core/utils/formatText.ts index f234083..06cf862 100644 --- a/packages/core/utils/formatText.ts +++ b/packages/core/utils/formatText.ts @@ -4,8 +4,11 @@ export default function(key: string){ "audio/ogg": "OGG 容器", "audio/flac": "FLAC 无损音频", "audio/aac": "AAC 音频", + "audio/wav": "WAV 音频", + "ttml": "TTML歌词", "lrc": "LRC 歌词" } if (!key) return "未知格式"; + if (!(key in dict)) return key; else return dict[key as keyof typeof dict]; } \ No newline at end of file diff --git a/packages/core/utils/getCurrentTimestamp.ts b/packages/core/utils/getCurrentTimestamp.ts new file mode 100644 index 0000000..fde98b4 --- /dev/null +++ b/packages/core/utils/getCurrentTimestamp.ts @@ -0,0 +1,4 @@ +export default function timestamp() { + const ts = new Date().getTime(); + return ts; +} \ No newline at end of file diff --git a/packages/web/src/app.html b/packages/web/src/app.html index 1b2d7be..876c155 100644 --- a/packages/web/src/app.html +++ b/packages/web/src/app.html @@ -2,12 +2,9 @@ - - - - + AquaVox %sveltekit.head% diff --git a/packages/web/src/routes/+page.svelte b/packages/web/src/routes/+page.svelte index 804fa72..ffe73ba 100644 --- a/packages/web/src/routes/+page.svelte +++ b/packages/web/src/routes/+page.svelte @@ -67,8 +67,15 @@ class="relative my-4 p-4 duration-150 bg-zinc-200 hover:bg-zinc-300 dark:bg-zinc-700 dark:hover:bg-zinc-600 rounded-lg" > {musicList[id].name}
- {toHumanSize(musicList[id].size)} · - 导入歌词 +
+ {toHumanSize(musicList[id].size)} +
{location.href = `/import/${id}/lyric`;e.stopPropagation();}}> + 导入歌词 +
+
+ {#if musicList[id].coverUrl} diff --git a/packages/web/src/routes/play/[id]/+page.svelte b/packages/web/src/routes/play/[id]/+page.svelte index af33885..59ec353 100644 --- a/packages/web/src/routes/play/[id]/+page.svelte +++ b/packages/web/src/routes/play/[id]/+page.svelte @@ -14,6 +14,7 @@ import progressBarRaw from '@core/state/progressBarRaw'; import { parseTTML, parseLRC } from '@alikia/aqualyrics'; import NewLyrics from '@core/components/lyrics/newLyrics.svelte'; + import timestamp from '@core/utils/getCurrentTimestamp'; const audioId = $page.params.id; let audioPlayer: HTMLAudioElement | null = null; @@ -31,6 +32,7 @@ let hasLyrics: boolean; const coverPath = writable(''); let mainInterval: ReturnType; + let showInteractiveBoxUntil = Infinity; let showInteractiveBox = true; function setMediaSession() { @@ -197,8 +199,8 @@ } } - function setShowingInteractiveBox(showing: boolean) { - showInteractiveBox = showing; + function showingInteractiveBoxUntil(until: number) { + showInteractiveBoxUntil = until; } $: { @@ -224,6 +226,10 @@ $: hasLyrics = !!originalLyrics; + setInterval(() => { + showInteractiveBox = timestamp() < showInteractiveBoxUntil || screen.width > 728; + }, 500); + readDB(); @@ -231,41 +237,45 @@ {name} - AquaVox - - - +
- + + + - + +
diff --git a/packages/web/static/icon.png b/packages/web/static/icon.png deleted file mode 100644 index 4087c656b069447adda3b81c699c4db1b3d83355..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9692 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 VJd - - - - - - - - + \ No newline at end of file