44 lines
1.6 KiB
TypeScript
44 lines
1.6 KiB
TypeScript
import { nextTick, ref } from 'vue'
|
|
|
|
/**
|
|
* First-paint entrance shared by the tab screens (Home, Dashboard, Cart):
|
|
* once a screen's first data load lands, its sections rise and fade in one
|
|
* after another. The styles are the global `.reveal` / `.is-revealed` pair in
|
|
* assets/styles/style.less.
|
|
*
|
|
* MasterLayout keeps the main tabs alive, but a screen still remounts after a
|
|
* full-page route or if it is not in the keep-alive list. Which screens have
|
|
* already played is kept here in module scope, so the entrance is a first-run
|
|
* flourish that resets only on a full reload — replaying it on each return
|
|
* would fight the page transition rather than add to it. (A `let` at the top of <script setup>
|
|
* would not do this: that code runs once per mount.)
|
|
*/
|
|
const played = new Set<string>()
|
|
|
|
export function useReveal(key: string, stepMs = 70) {
|
|
const revealed = ref(played.has(key))
|
|
|
|
// Each item starts a beat after the one before it. Kept small, so the whole
|
|
// sequence finishes well under a second and reads as the page arriving.
|
|
function revealDelay(i: number) {
|
|
return { '--reveal-delay': `${i * stepMs}ms` }
|
|
}
|
|
|
|
// Call once the first load has populated the DOM. Waits for the new nodes
|
|
// to be mounted, then for one painted frame in their "before" state — with
|
|
// a single frame the browser can resolve both states in the same style pass
|
|
// and there is nothing to animate.
|
|
async function reveal() {
|
|
if (revealed.value) return
|
|
await nextTick()
|
|
requestAnimationFrame(() =>
|
|
requestAnimationFrame(() => {
|
|
revealed.value = true
|
|
played.add(key)
|
|
}),
|
|
)
|
|
}
|
|
|
|
return { revealed, revealDelay, reveal }
|
|
}
|