Files
KVideo/lib/hooks/useMediaQuery.ts
T

120 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* useMediaQuery Hook
* 媒体查询 Hook - 监听浏览器媒体查询状态变化
*
* 遵循 Liquid Glass 设计系统原则:
* - 响应式设计优先
* - 性能优化(使用 matchMedia API
* - SSR 兼容
*/
'use client';
import { useState, useEffect } from 'react';
/**
* 使用媒体查询 Hook
* @param query - CSS 媒体查询字符串(例如:'(min-width: 768px)'
* @returns boolean - 媒体查询是否匹配
*
* @example
* ```tsx
* // 检测是否为移动设备
* const isMobile = useMediaQuery('(max-width: 768px)');
*
* // 检测是否为暗色模式
* const isDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
*
* // 检测是否为触摸设备
* const isTouchDevice = useMediaQuery('(hover: none) and (pointer: coarse)');
* ```
*/
export function useMediaQuery(query: string): boolean {
// 初始状态处理(SSR 兼容)
const getMatches = (query: string): boolean => {
// 防止 SSR 错误
if (typeof window === 'undefined') {
return false;
}
return window.matchMedia(query).matches;
};
const [matches, setMatches] = useState<boolean>(getMatches(query));
useEffect(() => {
// 防止 SSR 错误
if (typeof window === 'undefined') {
return;
}
const mediaQuery = window.matchMedia(query);
// 更新状态的处理函数
const handleChange = (event: MediaQueryListEvent) => {
setMatches(event.matches);
};
// 初始化时检查一次
setMatches(mediaQuery.matches);
// 添加监听器(现代浏览器使用 addEventListener
if (mediaQuery.addEventListener) {
mediaQuery.addEventListener('change', handleChange);
} else {
// 兼容旧版浏览器(已废弃的 API)
// @ts-ignore
mediaQuery.addListener(handleChange);
}
// 清理函数
return () => {
if (mediaQuery.removeEventListener) {
mediaQuery.removeEventListener('change', handleChange);
} else {
// @ts-ignore
mediaQuery.removeListener(handleChange);
}
};
}, [query]);
return matches;
}
/**
* 预定义的断点常量(基于 Tailwind CSS
* 与 Liquid Glass 设计系统的响应式规范一致
*/
export const BREAKPOINTS = {
sm: '(min-width: 640px)',
md: '(min-width: 768px)',
lg: '(min-width: 1024px)',
xl: '(min-width: 1280px)',
'2xl': '(min-width: 1536px)',
// 反向查询(小于某个断点)
maxSm: '(max-width: 639px)',
maxMd: '(max-width: 767px)',
maxLg: '(max-width: 1023px)',
maxXl: '(max-width: 1279px)',
max2xl: '(max-width: 1535px)',
// 特殊查询
mobile: '(max-width: 768px)',
tablet: '(min-width: 768px) and (max-width: 1024px)',
desktop: '(min-width: 1024px)',
touch: '(hover: none) and (pointer: coarse)',
darkMode: '(prefers-color-scheme: dark)',
lightMode: '(prefers-color-scheme: light)',
reducedMotion: '(prefers-reduced-motion: reduce)',
} as const;
/**
* 便捷的断点检测 Hooks
*/
export const useIsMobile = () => useMediaQuery(BREAKPOINTS.mobile);
export const useIsTablet = () => useMediaQuery(BREAKPOINTS.tablet);
export const useIsDesktop = () => useMediaQuery(BREAKPOINTS.desktop);
export const useIsTouchDevice = () => useMediaQuery(BREAKPOINTS.touch);
export const usePrefersDarkMode = () => useMediaQuery(BREAKPOINTS.darkMode);
export const usePrefersReducedMotion = () => useMediaQuery(BREAKPOINTS.reducedMotion);