feat(lyrics): smoother transitions and static lyrics at top
- Reduced transition durations from 300-400ms to 120-150ms - Changed easing to ease-out for snappier feel - Added will-change for GPU acceleration - Non-synced lyrics now display at container top (no spacers) - Added isSynced prop to all lyrics components
This commit is contained in:
1 parent
b171a17b57
commit
340fb2a72a
6 files changed
+45
-14
No files matched your search
@@ -23,6 +23,7 @@
|
||||
lyricsLines?: LyricsLine[];
|
||||
lyricsActiveIndex?: number;
|
||||
lyricsActiveProgress?: number;
|
||||
lyricsSynced?: boolean;
|
||||
lyricsLoading?: boolean;
|
||||
lyricsError?: string | null;
|
||||
}
|
||||
@@ -43,6 +44,7 @@
|
||||
lyricsLines = [],
|
||||
lyricsActiveIndex = -1,
|
||||
lyricsActiveProgress = 0,
|
||||
lyricsSynced = false,
|
||||
lyricsLoading = false,
|
||||
lyricsError = null
|
||||
}: Props = $props();
|
||||
@@ -141,6 +143,7 @@
|
||||
lines={lyricsLines}
|
||||
activeIndex={lyricsActiveIndex}
|
||||
activeProgress={lyricsActiveProgress}
|
||||
isSynced={lyricsSynced}
|
||||
center={false}
|
||||
compact={false}
|
||||
immersive={true}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
lines: LyricsLine[];
|
||||
activeIndex?: number;
|
||||
activeProgress?: number;
|
||||
isSynced?: boolean;
|
||||
align?: 'left' | 'center';
|
||||
title?: string;
|
||||
artist?: string;
|
||||
@@ -19,6 +20,7 @@
|
||||
lines,
|
||||
activeIndex = -1,
|
||||
activeProgress = 0,
|
||||
isSynced = false,
|
||||
align = 'center',
|
||||
title = '',
|
||||
artist = '',
|
||||
@@ -54,6 +56,7 @@
|
||||
{lines}
|
||||
{activeIndex}
|
||||
{activeProgress}
|
||||
{isSynced}
|
||||
center={isCenter}
|
||||
compact={false}
|
||||
/>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
compact?: boolean;
|
||||
scrollToActive?: boolean;
|
||||
immersive?: boolean;
|
||||
isSynced?: boolean;
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -24,7 +25,8 @@
|
||||
center = false,
|
||||
compact = false,
|
||||
scrollToActive = true,
|
||||
immersive = false
|
||||
immersive = false,
|
||||
isSynced = false
|
||||
}: Props = $props();
|
||||
|
||||
let container: HTMLDivElement | null = null;
|
||||
@@ -99,18 +101,21 @@
|
||||
class:compact
|
||||
class:center
|
||||
class:immersive
|
||||
class:static={!isSynced}
|
||||
bind:this={container}
|
||||
>
|
||||
{#if lines.length === 0}
|
||||
<div class="lyrics-empty">No lyrics available</div>
|
||||
{:else}
|
||||
<!-- Spacer at top to allow first lines to scroll to center -->
|
||||
<div class="lyrics-spacer"></div>
|
||||
<!-- Spacer at top to allow first lines to scroll to center (only for synced) -->
|
||||
{#if isSynced}
|
||||
<div class="lyrics-spacer"></div>
|
||||
{/if}
|
||||
|
||||
{#each lines as line, index}
|
||||
{@const isActive = index === activeIndex}
|
||||
{@const isPast = index < activeIndex}
|
||||
{@const opacity = getLineOpacity(index, activeIndex)}
|
||||
{@const isActive = isSynced && index === activeIndex}
|
||||
{@const isPast = isSynced && index < activeIndex}
|
||||
{@const opacity = isSynced ? getLineOpacity(index, activeIndex) : 1}
|
||||
<div
|
||||
class="lyrics-line"
|
||||
class:active={isActive}
|
||||
@@ -122,8 +127,10 @@
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<!-- Spacer at bottom to allow last lines to scroll to center -->
|
||||
<div class="lyrics-spacer"></div>
|
||||
<!-- Spacer at bottom to allow last lines to scroll to center (only for synced) -->
|
||||
{#if isSynced}
|
||||
<div class="lyrics-spacer"></div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -135,7 +142,6 @@
|
||||
padding: 16px 20px;
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
scroll-behavior: smooth;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--bg-tertiary) transparent;
|
||||
}
|
||||
@@ -158,6 +164,16 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Static mode - non-synced lyrics, start at top */
|
||||
.lyrics-lines.static {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.lyrics-lines.static .lyrics-line {
|
||||
opacity: 0.85;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.lyrics-lines.center {
|
||||
text-align: center;
|
||||
}
|
||||
@@ -199,12 +215,13 @@
|
||||
letter-spacing: 0.01em;
|
||||
opacity: var(--line-opacity, 1);
|
||||
transition:
|
||||
opacity 400ms cubic-bezier(0.4, 0, 0.2, 1),
|
||||
transform 400ms cubic-bezier(0.4, 0, 0.2, 1),
|
||||
font-size 300ms cubic-bezier(0.4, 0, 0.2, 1),
|
||||
font-weight 300ms cubic-bezier(0.4, 0, 0.2, 1),
|
||||
color 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
opacity 150ms ease-out,
|
||||
transform 150ms ease-out,
|
||||
font-size 120ms ease-out,
|
||||
font-weight 120ms ease-out,
|
||||
color 150ms ease-out;
|
||||
transform-origin: left center;
|
||||
will-change: opacity, transform;
|
||||
}
|
||||
|
||||
.lyrics-lines.center .lyrics-line {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
lines: LyricsLine[];
|
||||
activeIndex?: number;
|
||||
activeProgress?: number;
|
||||
isSynced?: boolean;
|
||||
title?: string;
|
||||
artist?: string;
|
||||
artwork?: string;
|
||||
@@ -23,6 +24,7 @@
|
||||
lines,
|
||||
activeIndex = -1,
|
||||
activeProgress = 0,
|
||||
isSynced = false,
|
||||
title = '',
|
||||
artist = '',
|
||||
artwork,
|
||||
@@ -43,6 +45,7 @@
|
||||
{lines}
|
||||
{activeIndex}
|
||||
{activeProgress}
|
||||
{isSynced}
|
||||
compact={true}
|
||||
center={false}
|
||||
/>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
lines: LyricsLine[];
|
||||
activeIndex?: number;
|
||||
activeProgress?: number;
|
||||
isSynced?: boolean;
|
||||
isLoading?: boolean;
|
||||
error?: string | null;
|
||||
}
|
||||
@@ -22,6 +23,7 @@
|
||||
lines,
|
||||
activeIndex = -1,
|
||||
activeProgress = 0,
|
||||
isSynced = false,
|
||||
isLoading = false,
|
||||
error = null
|
||||
}: Props = $props();
|
||||
@@ -53,6 +55,7 @@
|
||||
{lines}
|
||||
{activeIndex}
|
||||
{activeProgress}
|
||||
{isSynced}
|
||||
compact={true}
|
||||
center={false}
|
||||
/>
|
||||
|
||||
@@ -1102,6 +1102,7 @@
|
||||
lines={lyricsLines.map(l => ({ text: l.text }))}
|
||||
activeIndex={lyricsActiveIndex}
|
||||
activeProgress={lyricsActiveProgress}
|
||||
isSynced={lyricsIsSynced}
|
||||
isLoading={lyricsStatus === 'loading'}
|
||||
error={lyricsStatus === 'error' ? lyricsError : (lyricsStatus === 'not_found' ? 'No lyrics found' : null)}
|
||||
/>
|
||||
@@ -1215,6 +1216,7 @@
|
||||
lyricsLines={lyricsLines.map(l => ({ text: l.text }))}
|
||||
lyricsActiveIndex={lyricsActiveIndex}
|
||||
lyricsActiveProgress={lyricsActiveProgress}
|
||||
lyricsSynced={lyricsIsSynced}
|
||||
lyricsLoading={lyricsStatus === 'loading'}
|
||||
lyricsError={lyricsStatus === 'error' ? lyricsError : (lyricsStatus === 'not_found' ? 'No lyrics found' : null)}
|
||||
/>
|
||||
|
||||
Reference in new issue
Block a user