cb899a2913
Drop the --player-height bottom inset so the fixed visualizer fills the viewport; the inset player bar no longer leaves a page-background gap. The spacer now occludes via opaque page-surface + z-index. Visualizer no longer reads --player-height, so spacer.ts coalescing is removed.
65 lines
2.5 KiB
TypeScript
65 lines
2.5 KiB
TypeScript
/**
|
|
* Player-height spacer observer.
|
|
*
|
|
* The audio player docks `position: fixed` to the viewport bottom, so it sits
|
|
* outside normal flow and would overlap page content. A spacer div in the layout
|
|
* reserves the equivalent space — but the player's height is not constant: it
|
|
* reflows across the four breakpoints and grows when an error banner appears. A
|
|
* static height can't track that, so we mirror the player's live border-box
|
|
* height into the `--player-height` custom property on :root and let the spacer
|
|
* read it. One observer at a time, re-pointed on each `observe` call; the var
|
|
* resets to 0 on `unobserve` (player minimized / disposed) so the spacer
|
|
* collapses.
|
|
*
|
|
* SOLE CONSUMER (post visualizer-viewport-framing). `--player-height` now feeds
|
|
* ONLY the layout spacer div (MainLayout.razor.css `.player-spacer.expanded`).
|
|
* The ambient WaveformVisualizer backdrop is anchored `inset: 0` and no longer
|
|
* reads this var, so a height change here only resizes the spacer's `height` — a
|
|
* cheap, side-effect-free layout write. There is no GL backing store to clear and
|
|
* no theater-flash to debounce against, so we publish every observed frame
|
|
* directly: the spacer tracks the bar exactly through the Theater-Mode ease with
|
|
* no settle lag, and this stays the SOLE writer of `--player-height`.
|
|
*/
|
|
|
|
const HEIGHT_VAR = '--player-height';
|
|
|
|
let observer: ResizeObserver | null = null;
|
|
let lastWritten = -1;
|
|
|
|
function setVar(px: number): void {
|
|
// Round up so sub-pixel heights never leave a hairline of overlap.
|
|
const rounded = Math.ceil(px);
|
|
if (rounded === lastWritten) return;
|
|
lastWritten = rounded;
|
|
document.documentElement.style.setProperty(HEIGHT_VAR, `${rounded}px`);
|
|
}
|
|
|
|
function measure(entry: ResizeObserverEntry): number {
|
|
// Prefer the border-box measurement; fall back to contentRect on the
|
|
// (older) engines that don't populate borderBoxSize.
|
|
const box = entry.borderBoxSize?.[0];
|
|
return box ? box.blockSize : entry.contentRect.height;
|
|
}
|
|
|
|
export function observe(element: Element): void {
|
|
unobserve();
|
|
if (!element) return;
|
|
|
|
observer = new ResizeObserver(entries => {
|
|
const entry = entries[0];
|
|
if (!entry) return;
|
|
setVar(measure(entry));
|
|
});
|
|
observer.observe(element);
|
|
|
|
// Seed synchronously so the spacer is correct on this frame, before the
|
|
// first ResizeObserver callback fires.
|
|
setVar(element.getBoundingClientRect().height);
|
|
}
|
|
|
|
export function unobserve(): void {
|
|
observer?.disconnect();
|
|
observer = null;
|
|
setVar(0);
|
|
}
|