File size: 2,378 Bytes
676fc08
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
"use client";
import {
  useState,
  useRef,
  useEffect,
  type RefObject,
  type ReactNode,
} from "react";

type FloatingMenuProps = {
  targetRef: RefObject<HTMLDivElement | null>;
  children: ReactNode;
  //Offset from the top of the browser when fixed (optional)
  fixedTopOffset?: number;
  fixedRightOffset?: number;
};

function FloatingMenu({
  targetRef,
  children,
  fixedTopOffset = 10,
  fixedRightOffset = 0,
}: FloatingMenuProps) {
  const [isFixed, setIsFixed] = useState(false);
  const [fixedTop, setFixedTop] = useState<number>(0);
  const [fixedRight, setFixedRight] = useState<number>(0);
  const menuRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const handleScroll = () => {
      if (!targetRef.current || !menuRef.current) return;

      const targetRect = targetRef.current.getBoundingClientRect();

      // Determine whether the target Div is completely above the viewport (the top exceeds the screen)
      if (targetRect.bottom < 0) {
        // The target Div is completely scrolled out of the top, and the menu is no longer fixed
        setIsFixed(false);
        setFixedTop(0);
        setFixedRight(0);
      }
      // Determine whether the target Div is within the viewport or partially within the viewport
      else if (targetRect.top <= 0) {
        // The target Div enters the viewport and starts to be fixed
        setIsFixed(true);
        // Set a fixed top value
        setFixedTop(fixedTopOffset);
        // Set a fixed right value
        setFixedRight(targetRect.right - targetRect.width);
      } else {
        // The target Div is still above the viewport, the menu is not fixed
        setIsFixed(false);
        setFixedTop(0);
        setFixedRight(0);
      }
    };

    window.addEventListener("scroll", handleScroll);
    // Execute once immediately when the component is loaded to initialize the state
    handleScroll();

    return () => {
      window.removeEventListener("scroll", handleScroll);
    };
  }, [targetRef, fixedTopOffset]);

  return (
    <div
      ref={menuRef}
      style={{
        position: isFixed ? "fixed" : "absolute",
        top: isFixed ? fixedTop : 0,
        right: fixedRightOffset + (isFixed ? fixedRight : 0),
        // Make sure the menu is above the content
        zIndex: 30,
      }}
    >
      {children}
    </div>
  );
}

export default FloatingMenu;