1. /** 基础 hooks: useTip */
    2. const { useRef ,useState, useEffect, useCallback } = React;
    3. export interface TipState<T> {
    4. visible?: boolean;
    5. data?: T;
    6. }
    7. const useTip = <T>(initialState: T = {} as T) => {
    8. const [state, setState] = useState<TipState<T>>({
    9. visible: false,
    10. data: initialState,
    11. });
    12. const showTip = useCallback(
    13. (data: T) => {
    14. setState({ visible: true, data });
    15. },
    16. [setState],
    17. );
    18. const hideTip = useCallback(() => {
    19. setState({
    20. visible: false,
    21. data: initialState,
    22. });
    23. }, [setState]);
    24. return [state, setState, { showTip, hideTip }] as const;
    25. };
    26. /** useMouseMove */
    27. const useMouseMove = (
    28. ref: React.MutableRefObject<Element | null>,
    29. eventName: string,
    30. handler: EventListenerOrEventListenerObject,
    31. options?: boolean | AddEventListenerOptions,
    32. ) => {
    33. useEffect(() => {
    34. if (!ref?.current?.addEventListener) {
    35. console.log('未匹配到正确的属性');
    36. return;
    37. }
    38. if (!eventName) {
    39. console.error('没有监听事件类型');
    40. return;
    41. }
    42. ref.current.addEventListener(eventName, handler, options);
    43. return () => {
    44. ref.current?.removeEventListener(eventName, handler, options);
    45. };
    46. }, [ref, eventName]);
    47. };
    48. /** usePosTip: 计算错误 */
    49. type OffsetPos = [number, number];
    50. interface PosState extends Omit<DOMRect, 'toJSON'> {
    51. offsetY?: number;
    52. offsetX?: number;
    53. }
    54. const initState: PosState = {
    55. x: 0,
    56. y: 0,
    57. top: 0,
    58. left: 0,
    59. right: 0,
    60. width: 0,
    61. bottom: 0,
    62. height: 0,
    63. /** 最终偏移量 */
    64. offsetX: undefined,
    65. offsetY: undefined,
    66. };
    67. const initOffset: OffsetPos = [16, 24];
    68. const getContentRect = <T extends HTMLElement>(dom: T, pos: OffsetPos) => {
    69. const rect = dom.getBoundingClientRect() as PosState;
    70. const [x, y] = pos;
    71. const offsetX = rect.x + x;
    72. const offsetY = rect.y + y;
    73. return Object.assign(rect, {
    74. offsetX,
    75. offsetY,
    76. });
    77. };
    78. /** containerRef: 计算错误, 应使用 event.x 和 event.y */
    79. const usePosTip = <T extends HTMLElement>(
    80. containerRef: React.MutableRefObject<Element | null>,
    81. offsetPos = initOffset,
    82. ) => {
    83. const ref = React.useRef<T | null>(null);
    84. const [state, setState, { showTip, hideTip }] = useTip<PosState>(initState);
    85. useMouseMove(containerRef, 'mousemove', event => {
    86. if (ref.current && containerRef.current) {
    87. const dom = containerRef.current;
    88. const contentDom = ref.current;
    89. const { clientHeight, clientWidth } = dom;
    90. const contentRect = getContentRect(contentDom, offsetPos);
    91. const { width, height, offsetX, offsetY } = contentRect;
    92. const newX = width + offsetX >= clientWidth ? clientWidth - offsetX + 10 : offsetX;
    93. const newY = height + offsetY >= clientHeight ? clientHeight - offsetY + 10 : offsetY;
    94. const rect = {
    95. ...contentRect,
    96. offsetY: newX,
    97. offsetX: newY,
    98. };
    99. showTip(rect);
    100. }
    101. });
    102. useMouseMove(containerRef, 'mouseout', hideTip);
    103. return [ref, state, setState, { showTip, hideTip }] as const;
    104. };
    105. const Demo = () => {
    106. const containerRef = useRef<HTMLDivElement | null>(null);
    107. const [ref, state, , { showTip, hideTip }] = usePosTip<HTMLSpanElement>(containerRef);
    108. return (
    109. <div className="wrapper" ref={containerRef}>
    110. <pre ref={ref}>{JSON.stringify(state, null, 2)}</pre>
    111. </div>
    112. );
    113. };
    114. ReactDOM.render(<Demo />, mountNode);