ARTICLE AD BOX
I can't get Floating UI with React to work the way I want it to. I've created a popup component that follows the mouse on mouse move. I can set the pinnedAt prop to cause the popup to pin in place but Floating UI continues to update the position of the pinned popup when its container is scrolled so that it remains in place relative to the viewport. I want it to, once it's pinned, scroll with the container.
How can I do this? Here's my component code:
import scopedCss from '@/components/TechNodePopup.module.css'; import { useEffect, useRef } from "react"; import clsx from "clsx"; import { useFloating, offset, flip, shift, autoUpdate } from "@floating-ui/react"; import { getAlignment } from "@floating-ui/utils"; import type { AbilityType } from "@/types/AbilityType"; import type { TechNodeWithMeta } from "@/types/TechNodeWithMeta"; import TechNodePopupContent from './TechNodePopupContent'; export interface TechNodePopupProps { abilityType: AbilityType; abilityName?: string; nodeData: TechNodeWithMeta; getTechNameFromId: (id: string) => string; initialX: number; initialY: number; pinnedAt?: { x: number; y: number }; } export default function TechNodePopup({ abilityType, abilityName, nodeData, getTechNameFromId, initialX, initialY, pinnedAt }: TechNodePopupProps) { const mouseRef = useRef({ x: initialX, y: initialY }); const { refs, floatingStyles, update, } = useFloating({ strategy: "absolute", placement: "top-end", middleware: [ offset(({ placement }) => { return { mainAxis: 20, crossAxis: getAlignment(placement) === "start" ? 20 : -20 }; }), flip({ mainAxis: true, crossAxis: true, }), shift({ mainAxis: true, crossAxis: true, }), ], whileElementsMounted: autoUpdate, }); // Virtual element representing the mouse cursor useEffect(() => { refs.setReference({ getBoundingClientRect() { const pos = pinnedAt ?? mouseRef.current; return { x: pos.x, y: pos.y, top: pos.y, left: pos.x, right: pos.x, bottom: pos.y, width: 0, height: 0, }; }, }); }, [refs, pinnedAt]); // Get floating element to position itself based on mouse cursor upon mouse movement useEffect(() => { function handleMouseMove(e: MouseEvent) { mouseRef.current = { x: e.clientX, y: e.clientY }; if (pinnedAt) { return; } update(); } window.addEventListener("mousemove", handleMouseMove); return () => { window.removeEventListener("mousemove", handleMouseMove); }; }, [update, pinnedAt]); // Calc tech type class (needed here and in content component) let techTypeClass = ""; if (abilityType === "tech") { techTypeClass = clsx(nodeData.techType && `tech-${nodeData.techType.toLowerCase()}`); } return ( <div ref={refs.setFloating} className={clsx("techNode", scopedCss.techNode, "selected", "highlighted", techTypeClass, pinnedAt && scopedCss.pinned)} style={{ ...floatingStyles, width: "410px", zIndex: 999, }} > <TechNodePopupContent abilityType={abilityType} abilityName={abilityName} techTypeClass={techTypeClass} nodeData={nodeData} getTechNameFromId={getTechNameFromId} /> </div> ); }