Files
wr.do/hooks/use-window-size.ts
2025-10-20 11:34:05 +08:00

94 lines
2.4 KiB
TypeScript

"use client"
import * as React from "react"
import { useThrottledCallback } from "./use-throttled-callback"
export interface WindowSizeState {
/**
* The width of the window's visual viewport in pixels.
*/
width: number
/**
* The height of the window's visual viewport in pixels.
*/
height: number
/**
* The distance from the top of the visual viewport to the top of the layout viewport.
* Particularly useful for handling mobile keyboard appearance.
*/
offsetTop: number
/**
* The distance from the left of the visual viewport to the left of the layout viewport.
*/
offsetLeft: number
/**
* The scale factor of the visual viewport.
* This is useful for scaling elements based on the current zoom level.
*/
scale: number
}
/**
* Hook that tracks the window's visual viewport dimensions, position, and provides
* a CSS transform for positioning elements.
*
* Uses the Visual Viewport API to get accurate measurements, especially important
* for mobile devices where virtual keyboards can change the visible area.
* Only updates state when values actually change to optimize performance.
*
* @returns An object containing viewport properties and a CSS transform string
*/
export function useWindowSize(): WindowSizeState {
const [windowSize, setWindowSize] = React.useState<WindowSizeState>({
width: 0,
height: 0,
offsetTop: 0,
offsetLeft: 0,
scale: 0,
})
const handleViewportChange = useThrottledCallback(() => {
if (typeof window === "undefined") return
const vp = window.visualViewport
if (!vp) return
const {
width = 0,
height = 0,
offsetTop = 0,
offsetLeft = 0,
scale = 0,
} = vp
setWindowSize((prevState) => {
if (
width === prevState.width &&
height === prevState.height &&
offsetTop === prevState.offsetTop &&
offsetLeft === prevState.offsetLeft &&
scale === prevState.scale
) {
return prevState
}
return { width, height, offsetTop, offsetLeft, scale }
})
}, 200)
React.useEffect(() => {
const visualViewport = window.visualViewport
if (!visualViewport) return
visualViewport.addEventListener("resize", handleViewportChange)
handleViewportChange()
return () => {
visualViewport.removeEventListener("resize", handleViewportChange)
}
}, [handleViewportChange])
return windowSize
}