Skip to content

Commit 7da27d8

Browse files
committed
feat: Implement a mobile-optimized blurred background and refactor the crossfading avatar to use plain image tags.
1 parent d965942 commit 7da27d8

1 file changed

Lines changed: 76 additions & 36 deletions

File tree

client/src/Pages/Music/BottomPlayer/NowPlayingTab.jsx

Lines changed: 76 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,39 @@ const useImageColors = (imageSrc) => {
6363
return colors
6464
}
6565

66+
const useBlurredBg = (imageSrc) => {
67+
const [bgUrls, setBgUrls] = useState({ current: null, previous: null, transitioning: false })
68+
const canvasRef = useRef(null)
69+
const timeoutRef = useRef(null)
70+
const prevSrc = useRef(null)
71+
72+
useEffect(() => {
73+
if (!imageSrc || imageSrc === prevSrc.current) return
74+
prevSrc.current = imageSrc
75+
const img = new Image()
76+
img.crossOrigin = "anonymous"
77+
img.onload = () => {
78+
if (!canvasRef.current) canvasRef.current = document.createElement("canvas")
79+
const canvas = canvasRef.current
80+
canvas.width = 64
81+
canvas.height = 64
82+
const ctx = canvas.getContext("2d")
83+
ctx.filter = "blur(4px) saturate(1.3) brightness(0.65)"
84+
ctx.drawImage(img, -4, -4, 72, 72)
85+
const dataUrl = canvas.toDataURL("image/jpeg", 0.7)
86+
setBgUrls((prev) => ({ current: dataUrl, previous: prev.current, transitioning: true }))
87+
if (timeoutRef.current) clearTimeout(timeoutRef.current)
88+
timeoutRef.current = setTimeout(() => {
89+
setBgUrls((prev) => ({ ...prev, previous: null, transitioning: false }))
90+
}, 800)
91+
}
92+
img.src = imageSrc
93+
return () => { if (timeoutRef.current) clearTimeout(timeoutRef.current) }
94+
}, [imageSrc])
95+
96+
return bgUrls
97+
}
98+
6699
const useCrossfadeImage = (src) => {
67100
const [images, setImages] = useState({ current: src, previous: null, transitioning: false })
68101
const timeoutRef = useRef(null)
@@ -121,31 +154,22 @@ const UpNextHint = memo(({ nextSong }) => {
121154
UpNextHint.displayName = "UpNextHint"
122155

123156
const CrossfadeAvatar = memo(({ images, size, shadow, name }) => (
124-
<div className="relative np-img-hover" style={{ width: size, height: size }}>
157+
<div className="relative np-img-hover overflow-hidden rounded-2xl" style={{ width: size, height: size, boxShadow: shadow }}>
158+
<div className="absolute inset-0 flex items-center justify-center bg-white/5">
159+
<Music className="w-24 h-24 text-white/20" />
160+
</div>
125161
{images.previous && (
126-
<Avatar
127-
className="rounded-2xl absolute inset-0"
128-
style={{ width: size, height: size, boxShadow: shadow }}
129-
>
130-
<AvatarImage
131-
src={images.previous}
132-
className="object-cover np-fade-out"
133-
/>
134-
</Avatar>
135-
)}
136-
<Avatar
137-
className="rounded-2xl relative"
138-
style={{ width: size, height: size, boxShadow: shadow }}
139-
>
140-
<AvatarImage
141-
src={images.current}
142-
alt={name}
143-
className={`object-cover ${images.transitioning ? "np-fade-in" : ""}`}
162+
<img
163+
src={images.previous}
164+
className="absolute inset-0 w-full h-full object-cover np-fade-out"
165+
aria-hidden="true"
144166
/>
145-
<AvatarFallback className="text-6xl bg-white/5">
146-
<Music className="w-24 h-24 text-white/20" />
147-
</AvatarFallback>
148-
</Avatar>
167+
)}
168+
<img
169+
src={images.current}
170+
alt={name}
171+
className={`absolute inset-0 w-full h-full object-cover ${images.transitioning ? "np-fade-in" : ""}`}
172+
/>
149173
</div>
150174
))
151175
CrossfadeAvatar.displayName = "CrossfadeAvatar"
@@ -154,6 +178,7 @@ const NowPlayingTab = memo(({ currentSong }) => {
154178
const songImage = useMemo(() => currentSong?.image?.[2]?.link, [currentSong])
155179
const colors = useImageColors(songImage)
156180
const images = useCrossfadeImage(songImage)
181+
const mobileBg = useBlurredBg(songImage)
157182
const nextSong = useNextSong(currentSong)
158183
const hasAnimated = useRef(false)
159184
const [showEntrance, setShowEntrance] = useState(false)
@@ -196,42 +221,57 @@ const NowPlayingTab = memo(({ currentSong }) => {
196221

197222
return (
198223
<div className="w-full h-full relative overflow-hidden bg-[#050508]">
199-
{/* Layer 1: Album art dissolved into pure color fields */}
224+
225+
{/* === MOBILE BG: Pre-blurred canvas, no CSS filter === */}
226+
{mobileBg.previous && (
227+
<div
228+
className="lg:hidden absolute inset-0 bg-cover bg-center np-bg-fade-out"
229+
style={{ backgroundImage: `url(${mobileBg.previous})` }}
230+
/>
231+
)}
232+
{mobileBg.current && (
233+
<div
234+
className={`lg:hidden absolute inset-0 bg-cover bg-center ${mobileBg.transitioning ? "np-bg-fade-in" : ""}`}
235+
style={{ backgroundImage: `url(${mobileBg.current})` }}
236+
/>
237+
)}
238+
239+
{/* === DESKTOP BG: Full liquid glass with CSS blur === */}
200240
{images.previous && (
201241
<div
202-
className="absolute inset-0 bg-cover bg-center np-bg-fade-out"
242+
className="hidden lg:block absolute inset-0 bg-cover bg-center np-bg-fade-out"
203243
style={{ backgroundImage: `url(${images.previous})`, filter: "blur(160px) saturate(1.4) brightness(0.65)", transform: "scale(2)" }}
204244
/>
205245
)}
206246
<div
207-
className={`absolute inset-0 bg-cover bg-center ${images.transitioning ? "np-bg-fade-in" : ""}`}
247+
className={`hidden lg:block absolute inset-0 bg-cover bg-center ${images.transitioning ? "np-bg-fade-in" : ""}`}
208248
style={{ backgroundImage: `url(${images.current})`, filter: "blur(160px) saturate(1.4) brightness(0.65)", transform: "scale(2)", transition: "background-image 0.6s ease" }}
209249
/>
210250

211-
{/* Layer 2: Glass noise texture */}
212-
<div className="absolute inset-0 np-glass-noise" />
251+
{/* Desktop-only: glass noise */}
252+
<div className="hidden lg:block absolute inset-0 np-glass-noise" />
213253

214-
{/* Layer 3: Specular highlights — top-left and bottom-right light catches */}
215-
<div className="absolute inset-0" style={{
254+
{/* Desktop-only: specular highlights */}
255+
<div className="hidden lg:block absolute inset-0" style={{
216256
background: "radial-gradient(ellipse 70% 50% at 25% 20%, rgba(255,255,255,0.08) 0%, transparent 50%)"
217257
}} />
218-
<div className="absolute inset-0" style={{
258+
<div className="hidden lg:block absolute inset-0" style={{
219259
background: "radial-gradient(ellipse 50% 35% at 75% 75%, rgba(255,255,255,0.04) 0%, transparent 40%)"
220260
}} />
221261

222-
{/* Layer 4: Animated liquid shimmer */}
223-
<div className="absolute inset-0 np-orb-layer">
262+
{/* Desktop-only: animated liquid shimmer */}
263+
<div className="hidden lg:block absolute inset-0 np-orb-layer">
224264
<div className="np-orb np-o1" style={orbColor(c1, 0.18)} />
225265
<div className="np-orb np-o2" style={orbColor(c2, 0.14)} />
226266
<div className="np-shimmer" />
227267
</div>
228268

229-
{/* Layer 5: Luminous depth — glass curvature effect */}
230-
<div className="absolute inset-0" style={{
269+
{/* Desktop-only: luminous depth */}
270+
<div className="hidden lg:block absolute inset-0" style={{
231271
background: "radial-gradient(ellipse 120% 80% at 50% 40%, rgba(255,255,255,0.03) 0%, transparent 50%)"
232272
}} />
233273

234-
{/* Layer 6: Vignette */}
274+
{/* Shared: vignette (single gradient, very light on GPU) */}
235275
<div className="absolute inset-0" style={{
236276
background: "radial-gradient(ellipse at 50% 45%, transparent 30%, rgba(5,5,8,0.3) 60%, rgba(5,5,8,0.85) 100%)"
237277
}} />

0 commit comments

Comments
 (0)