ik hou van kaas
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import Image from "next/image"
|
||||
|
||||
interface RoomBackgroundProps {
|
||||
src: string
|
||||
durationMs?: number
|
||||
}
|
||||
|
||||
interface Layer {
|
||||
id: number
|
||||
src: string
|
||||
}
|
||||
|
||||
// Stacks background images and crossfades between them whenever `src`
|
||||
// changes. Each new layer only starts fading in once it has actually
|
||||
// finished loading, and layers below a fully-faded-in layer are dropped
|
||||
// on transitionend rather than a guessed timeout — this keeps rapid
|
||||
// room switches (and slow/cold-cache images) visually consistent.
|
||||
export function RoomBackground({ src, durationMs = 700 }: RoomBackgroundProps) {
|
||||
const idRef = useRef(0)
|
||||
const [layers, setLayers] = useState<Layer[]>(() => [{ id: idRef.current, src }])
|
||||
const [revealed, setRevealed] = useState<Set<number>>(() => new Set([idRef.current]))
|
||||
|
||||
useEffect(() => {
|
||||
setLayers((prev) => {
|
||||
if (prev[prev.length - 1]?.src === src) return prev
|
||||
idRef.current += 1
|
||||
return [...prev, { id: idRef.current, src }]
|
||||
})
|
||||
}, [src])
|
||||
|
||||
const reveal = (id: number) => {
|
||||
setRevealed((prev) => {
|
||||
if (prev.has(id)) return prev
|
||||
const next = new Set(prev)
|
||||
next.add(id)
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
const handleTopLayerTransitionEnd = (id: number) => {
|
||||
// Once the newest layer has finished fading in, everything below it
|
||||
// is fully hidden and can be dropped.
|
||||
setLayers((prev) => {
|
||||
const idx = prev.findIndex((l) => l.id === id)
|
||||
if (idx <= 0) return prev
|
||||
return prev.slice(idx)
|
||||
})
|
||||
}
|
||||
|
||||
const topId = layers[layers.length - 1]?.id
|
||||
|
||||
return (
|
||||
<div className="absolute inset-0 overflow-hidden">
|
||||
{layers.map((layer, i) => {
|
||||
const isTop = i === layers.length - 1
|
||||
const isRevealed = revealed.has(layer.id)
|
||||
return (
|
||||
<Image
|
||||
key={layer.id}
|
||||
src={layer.src || "/placeholder.svg"}
|
||||
alt=""
|
||||
fill
|
||||
priority
|
||||
className="object-cover transition-opacity ease-in-out"
|
||||
style={{
|
||||
transitionDuration: `${durationMs}ms`,
|
||||
opacity: isRevealed ? 1 : 0,
|
||||
}}
|
||||
onLoadingComplete={() => reveal(layer.id)}
|
||||
onTransitionEnd={() => {
|
||||
if (isTop && layer.id === topId) handleTopLayerTransitionEnd(layer.id)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user