improve: experience for mobile
This commit is contained in:
parent
2742ba43f2
commit
f1ecabd523
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "aquavox",
|
"name": "aquavox",
|
||||||
"version": "2.9.2",
|
"version": "2.9.3",
|
||||||
"private": false,
|
"private": false,
|
||||||
"module": "index.ts",
|
"module": "index.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
{#if hasLyrics}
|
{#if hasLyrics}
|
||||||
<img
|
<img
|
||||||
class="absolute shadow-md select-none z-10 object-cover rounded-lg md:rounded-2xl max-md:h-20 max-xl:h-32 max-xl:top-6 md:max-h-[calc(94vh-20rem)] xl:w-auto max-w-[90%] xl:max-w-[37vw]
|
class="absolute shadow-md select-none z-10 object-cover rounded-lg md:rounded-2xl max-md:h-20 max-xl:h-32 max-xl:top-6
|
||||||
|
md:max-h-[calc(94vh-20rem)] max-md:w-20 xl:w-auto max-w-[90%] xl:max-w-[37vw]
|
||||||
md:bottom-[21rem] left-6 md:left-[calc(7vw-1rem)] lg:left-[calc(12vw-1rem)] xl:translate-x-[-50%] xl:left-[25vw]"
|
md:bottom-[21rem] left-6 md:left-[calc(7vw-1rem)] lg:left-[calc(12vw-1rem)] xl:translate-x-[-50%] xl:left-[25vw]"
|
||||||
src={path}
|
src={path}
|
||||||
width="1200"
|
width="1200"
|
||||||
@ -19,7 +20,8 @@
|
|||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<img
|
<img
|
||||||
class="absolute shadow-md select-none z-10 object-cover rounded-2xl max-h-[calc(94vh-18rem)] md:max-h-[calc(94vh-20rem)] xl:w-auto max-w-[90%] md:max-w-[75%] xl:max-w-[37vw]
|
class="absolute shadow-md select-none z-10 object-cover rounded-2xl max-h-[calc(94vh-18rem)]
|
||||||
|
md:max-h-[calc(94vh-20rem)] xl:w-auto max-w-[90%] md:max-w-[75%] xl:max-w-[37vw]
|
||||||
bottom-72 md:bottom-80 left-1/2 translate-x-[-50%]"
|
bottom-72 md:bottom-80 left-1/2 translate-x-[-50%]"
|
||||||
src={path}
|
src={path}
|
||||||
alt="封面"
|
alt="封面"
|
||||||
|
@ -18,6 +18,9 @@
|
|||||||
|
|
||||||
export let hasLyrics: boolean;
|
export let hasLyrics: boolean;
|
||||||
|
|
||||||
|
export let showInteractiveBox: boolean;
|
||||||
|
export let setShowingInteractiveBox: Function;
|
||||||
|
|
||||||
let progressBar: HTMLDivElement;
|
let progressBar: HTMLDivElement;
|
||||||
let volumeBar: HTMLDivElement;
|
let volumeBar: HTMLDivElement;
|
||||||
let showInfoTop: boolean = false;
|
let showInfoTop: boolean = false;
|
||||||
@ -25,6 +28,13 @@
|
|||||||
let songInfoTopContainer: HTMLDivElement;
|
let songInfoTopContainer: HTMLDivElement;
|
||||||
let songInfoTopContent: HTMLSpanElement;
|
let songInfoTopContent: HTMLSpanElement;
|
||||||
let userAdjustingVolume = false;
|
let userAdjustingVolume = false;
|
||||||
|
let lastTouchClientX = 0;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (screen.width < 728) {
|
||||||
|
setShowingInteractiveBox(false);
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
const mql = window.matchMedia('(max-width: 1280px)');
|
const mql = window.matchMedia('(max-width: 1280px)');
|
||||||
|
|
||||||
@ -68,6 +78,30 @@
|
|||||||
$: {
|
$: {
|
||||||
showInfoTop = mql.matches && hasLyrics;
|
showInfoTop = mql.matches && hasLyrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.addEventListener("mousemove", (event) => {
|
||||||
|
if ($userAdjustingProgress) {
|
||||||
|
adjustDisplayProgress(event.offsetX / progressBar.getBoundingClientRect().width);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener("mouseup", (event) => {
|
||||||
|
if ($userAdjustingProgress) {
|
||||||
|
userAdjustingProgress.set(false);
|
||||||
|
adjustProgress(event.offsetX / progressBar.getBoundingClientRect().width);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener("touchmove", (event) => {
|
||||||
|
if ($userAdjustingProgress) {
|
||||||
|
adjustDisplayProgress((event.touches[0].clientX - progressBar.getBoundingClientRect().left) / progressBar.getBoundingClientRect().width);
|
||||||
|
lastTouchClientX = event.touches[0].clientX;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.addEventListener("touchend", (event) => {
|
||||||
|
if ($userAdjustingProgress) {
|
||||||
|
adjustProgress((lastTouchClientX - progressBar.getBoundingClientRect().left) / progressBar.getBoundingClientRect().width);
|
||||||
|
userAdjustingProgress.set(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if showInfoTop}
|
{#if showInfoTop}
|
||||||
@ -78,11 +112,14 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class={'absolute select-none bottom-12 h-60 w-[86vw] left-[7vw] z-10 ' +
|
class={'absolute select-none bottom-12 h-60 w-[86vw] left-[7vw] duration-500 z-10 ' +
|
||||||
(hasLyrics
|
(hasLyrics
|
||||||
? 'lg:w-[76vw] lg:left-[12vw] xl:w-[37vw] xl:left-[7vw]'
|
? 'lg:w-[76vw] lg:left-[12vw] xl:w-[37vw] xl:left-[7vw]'
|
||||||
: 'lg:w-[76vw] lg:left-[12vw] xl:w-[37vw] xl:left-[31.5vw]')}
|
: 'lg:w-[76vw] lg:left-[12vw] xl:w-[37vw] xl:left-[31.5vw]') + ' ' +
|
||||||
|
(showInteractiveBox ? 'opacity-100' : 'opacity-0')}
|
||||||
|
style={`z-index: ${showInteractiveBox ? "0" : "50"}`}
|
||||||
>
|
>
|
||||||
|
|
||||||
{#if !showInfoTop}
|
{#if !showInfoTop}
|
||||||
<div class="song-info">
|
<div class="song-info">
|
||||||
<div class="song-info-regular {isInfoTopOverflowing ? 'animate' : ''}" bind:this={songInfoTopContainer}>
|
<div class="song-info-regular {isInfoTopOverflowing ? 'animate' : ''}" bind:this={songInfoTopContainer}>
|
||||||
@ -95,6 +132,13 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<div class="absolute w-full h-2/3 bottom-0" style={`z-index: ${showInteractiveBox ? "0" : "50"}`} on:click={() => {
|
||||||
|
setShowingInteractiveBox(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setShowingInteractiveBox(false);
|
||||||
|
}, 5000);
|
||||||
|
}}></div>
|
||||||
|
|
||||||
<div class="progress top-16">
|
<div class="progress top-16">
|
||||||
<div class="time-indicator text-shadow-md time-current">
|
<div class="time-indicator text-shadow-md time-current">
|
||||||
{formatDuration(progress)}
|
{formatDuration(progress)}
|
||||||
@ -107,23 +151,14 @@
|
|||||||
class="progress-bar shadow-md"
|
class="progress-bar shadow-md"
|
||||||
on:keydown
|
on:keydown
|
||||||
on:keyup
|
on:keyup
|
||||||
|
on:click={(e) => {
|
||||||
|
progressBarOnClick(e);
|
||||||
|
}}
|
||||||
on:mousedown={() => {
|
on:mousedown={() => {
|
||||||
userAdjustingProgress.set(true);
|
userAdjustingProgress.set(true);
|
||||||
}}
|
}}
|
||||||
on:mousemove={(e) => {
|
on:touchstart={() => {
|
||||||
if ($userAdjustingProgress) {
|
userAdjustingProgress.set(true);
|
||||||
adjustDisplayProgress(e.offsetX / progressBar.getBoundingClientRect().width);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
on:mouseup={(e) => {
|
|
||||||
const offsetX = e.offsetX;
|
|
||||||
progressBarOnClick(e);
|
|
||||||
// Q: why it needs delay?
|
|
||||||
// A: I do not know.
|
|
||||||
setTimeout(()=> {
|
|
||||||
userAdjustingProgress.set(false);
|
|
||||||
progressBarMouseUp(offsetX);
|
|
||||||
}, 50);
|
|
||||||
}}
|
}}
|
||||||
role="slider"
|
role="slider"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
@ -16,10 +16,11 @@
|
|||||||
document.body.style.overflow = 'hidden';
|
document.body.style.overflow = 'hidden';
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
let { originalLyrics, progress, player } : {
|
let { originalLyrics, progress, player, showInteractiveBox } : {
|
||||||
originalLyrics: LyricData,
|
originalLyrics: LyricData,
|
||||||
progress: number,
|
progress: number,
|
||||||
player: HTMLAudioElement | null
|
player: HTMLAudioElement | null,
|
||||||
|
showInteractiveBox: boolean
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
// States
|
// States
|
||||||
@ -195,10 +196,10 @@
|
|||||||
|
|
||||||
lastEventLyricIndex = currentLyricIndex;
|
lastEventLyricIndex = currentLyricIndex;
|
||||||
lastEventProgress = progress;
|
lastEventProgress = progress;
|
||||||
if (!lyricChanged) return;
|
if (!lyricChanged || scrolling) return;
|
||||||
if (!lyricIndexDeltaTooBig && deltaInRange) {
|
if (!lyricIndexDeltaTooBig && deltaInRange) {
|
||||||
console.log("Event: regular move");
|
console.log("Event: regular move");
|
||||||
console.log(new Date().getTime() , lastSeekForward);
|
console.log(new Date().getTime(), lastSeekForward);
|
||||||
computeLayout();
|
computeLayout();
|
||||||
}
|
}
|
||||||
else if ($userAdjustingProgress) {
|
else if ($userAdjustingProgress) {
|
||||||
@ -257,8 +258,11 @@
|
|||||||
{#if originalLyrics && originalLyrics.scripts}
|
{#if originalLyrics && originalLyrics.scripts}
|
||||||
<div
|
<div
|
||||||
class="absolute top-[6.5rem] md:top-36 xl:top-0 w-screen xl:w-[52vw] px-6 md:px-12
|
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-[46vw] xl:px-[3vw] h-[calc(100vh-17rem)] xl:h-screen font-sans
|
lg:px-[7.5rem] xl:left-[46vw] xl:px-[3vw] xl:h-screen font-sans
|
||||||
text-left no-scrollbar z-[1] pt-16 overflow-hidden"
|
text-left no-scrollbar z-[1] pt-16 overflow-hidden"
|
||||||
|
style={`mask: linear-gradient(0deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 7%, rgba(0, 0, 0, 1) 95%,
|
||||||
|
rgba(0, 0, 0, 0) 100%);
|
||||||
|
height: ${showInteractiveBox ? "calc(100vh - 21rem)" : "calc(100vh - 7rem)"}`}
|
||||||
bind:this={lyricsContainer}
|
bind:this={lyricsContainer}
|
||||||
>
|
>
|
||||||
{#each lyricLines as lyric, i}
|
{#each lyricLines as lyric, i}
|
||||||
|
@ -2,8 +2,12 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<!-- <link rel="icon" href="%sveltekit.assets%/favicon.png" /> -->
|
<link rel="icon" href="/icon.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="manifest" href="/manifest.json" />
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
|
<link rel="apple-touch-icon" href="/icon.png">
|
||||||
<title>AquaVox</title>
|
<title>AquaVox</title>
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
|
@ -62,7 +62,7 @@
|
|||||||
{#each idList as id}
|
{#each idList as id}
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<div class="!no-underline !text-black dark:!text-white" onclick={() => goto(`/play/${id}`)}>
|
<div class="!no-underline !text-black dark:!text-white" onclick={() => location.href = (`/play/${id}`)}>
|
||||||
<li
|
<li
|
||||||
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"
|
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"
|
||||||
>
|
>
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
let hasLyrics: boolean;
|
let hasLyrics: boolean;
|
||||||
const coverPath = writable('');
|
const coverPath = writable('');
|
||||||
let mainInterval: ReturnType<typeof setInterval>;
|
let mainInterval: ReturnType<typeof setInterval>;
|
||||||
|
let showInteractiveBox = true;
|
||||||
|
|
||||||
function setMediaSession() {
|
function setMediaSession() {
|
||||||
if ('mediaSession' in navigator === false) return;
|
if ('mediaSession' in navigator === false) return;
|
||||||
@ -196,6 +197,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setShowingInteractiveBox(showing: boolean) {
|
||||||
|
showInteractiveBox = showing;
|
||||||
|
}
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
clearInterval(mainInterval);
|
clearInterval(mainInterval);
|
||||||
mainInterval = setInterval(() => {
|
mainInterval = setInterval(() => {
|
||||||
@ -240,9 +245,11 @@
|
|||||||
{adjustVolume}
|
{adjustVolume}
|
||||||
{adjustDisplayProgress}
|
{adjustDisplayProgress}
|
||||||
{hasLyrics}
|
{hasLyrics}
|
||||||
|
{showInteractiveBox}
|
||||||
|
{setShowingInteractiveBox}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NewLyrics {originalLyrics} progress={currentProgress} player={audioPlayer}/>
|
<NewLyrics {originalLyrics} progress={currentProgress} player={audioPlayer} {showInteractiveBox} />
|
||||||
|
|
||||||
<audio
|
<audio
|
||||||
bind:this={audioPlayer}
|
bind:this={audioPlayer}
|
||||||
|
BIN
packages/web/static/icon.png
Normal file
BIN
packages/web/static/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.5 KiB |
16
packages/web/static/manifest.json
Normal file
16
packages/web/static/manifest.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"orientation": "portrait",
|
||||||
|
"name": "AquaVox",
|
||||||
|
"short_name": "AquaVox",
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#000000",
|
||||||
|
"description": "A readable Hacker News app",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/icon.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user