53 lines
1.6 KiB
TypeScript
53 lines
1.6 KiB
TypeScript
import type { CSSProperties } from "react";
|
|
import { useEffect, useState } from "react";
|
|
|
|
export interface VisualViewportFrame {
|
|
readonly height: number;
|
|
readonly offsetTop: number;
|
|
}
|
|
|
|
const readVisualViewport = (): VisualViewportFrame => ({
|
|
height: Math.round(window.visualViewport?.height ?? window.innerHeight),
|
|
offsetTop: Math.round(window.visualViewport?.offsetTop ?? 0),
|
|
});
|
|
|
|
export const visualViewportStyle = (
|
|
frame?: VisualViewportFrame
|
|
): CSSProperties => ({
|
|
height: frame ? `${frame.height}px` : "100dvh",
|
|
transform: frame?.offsetTop ? `translateY(${frame.offsetTop}px)` : undefined,
|
|
});
|
|
|
|
export const useVisualViewportStyle = (): CSSProperties => {
|
|
const [frame, setFrame] = useState<VisualViewportFrame>();
|
|
|
|
useEffect(() => {
|
|
const root = document.documentElement;
|
|
const viewport = window.visualViewport;
|
|
let animationFrame = 0;
|
|
|
|
const update = () => {
|
|
cancelAnimationFrame(animationFrame);
|
|
animationFrame = requestAnimationFrame(() => {
|
|
setFrame(readVisualViewport());
|
|
});
|
|
};
|
|
|
|
root.classList.add("workspace-viewport-lock");
|
|
update();
|
|
window.addEventListener("resize", update);
|
|
viewport?.addEventListener("resize", update);
|
|
viewport?.addEventListener("scroll", update);
|
|
|
|
return () => {
|
|
cancelAnimationFrame(animationFrame);
|
|
root.classList.remove("workspace-viewport-lock");
|
|
window.removeEventListener("resize", update);
|
|
viewport?.removeEventListener("resize", update);
|
|
viewport?.removeEventListener("scroll", update);
|
|
};
|
|
}, []);
|
|
|
|
return visualViewportStyle(frame);
|
|
};
|