useSize.ts 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. import { useEffect, useState } from 'react';
  2. /**
  3. * 订阅 body 的宽高,在 window resize 时更新。
  4. * @returns 当前 body 的 { width, height }(clientWidth/clientHeight)
  5. */
  6. export const useScreenSize = () => {
  7. const [screenSize, setScreenSize] = useState<{ width: number; height: number }>({
  8. width: 0,
  9. height: 0,
  10. });
  11. useEffect(() => {
  12. const handleResize = () => {
  13. const body = document.getElementsByTagName('body')[0];
  14. setScreenSize({ width: body?.clientWidth ?? 0, height: body?.clientHeight ?? 0 });
  15. };
  16. handleResize();
  17. window.addEventListener('resize', handleResize);
  18. return () => window.removeEventListener('resize', handleResize);
  19. }, []);
  20. return screenSize;
  21. };
  22. /**
  23. * 订阅 ref 对应 DOM 元素的尺寸,使用 ResizeObserver 在尺寸变化时更新。
  24. * @param ref 目标元素的 RefObject
  25. * @returns 当前元素的 { width, height }(contentRect)
  26. */
  27. export const useRefSize = <T extends HTMLElement = HTMLDivElement>(
  28. ref: React.RefObject<T>
  29. ) => {
  30. const [size, setSize] = useState<{ width: number; height: number }>({ width: 0, height: 0 });
  31. useEffect(() => {
  32. const element = ref.current;
  33. if (!element) return;
  34. setSize({
  35. width: element.clientWidth,
  36. height: element.clientHeight,
  37. });
  38. const resizeObserver = new ResizeObserver((entries) => {
  39. for (const entry of entries) {
  40. const { width, height } = entry.contentRect;
  41. setSize({ width, height });
  42. }
  43. });
  44. resizeObserver.observe(element);
  45. return () => {
  46. resizeObserver.disconnect();
  47. };
  48. }, [ref]);
  49. return size;
  50. };
  51. const MOBILE_BREAKPOINT = 768;
  52. const PHONE_BREAKPOINT = 640;
  53. /**
  54. * 响应式检测:基于 useScreenSize 的 body 宽度判断设备类型。
  55. * - isMobile: 宽度 ≤ 768px(手机 + 平板)
  56. * - isPhone: 宽度 < 640px(仅手机,对应 Tailwind sm 断点以下)
  57. */
  58. export function useResponsive() {
  59. const { width } = useScreenSize();
  60. return {
  61. isMobile: width <= MOBILE_BREAKPOINT,
  62. isPhone: width > 0 && width < PHONE_BREAKPOINT,
  63. };
  64. }