From c3263aa9a2d046ba23edf8cbd9fdc86420d5faeb Mon Sep 17 00:00:00 2001 From: Kevin Qualters <56408403+kqualters-elastic@users.noreply.github.com> Date: Mon, 20 Jul 2020 17:41:25 -0400 Subject: [PATCH] [Security Solution][Resolver] Update the resolver element ref on scroll events if the position of the element has changed within the page (#72461) --- .../public/resolver/view/use_camera.ts | 54 +++++++++++++++++-- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/security_solution/public/resolver/view/use_camera.ts b/x-pack/plugins/security_solution/public/resolver/view/use_camera.ts index 6a1c35be57000..661e038d04e32 100644 --- a/x-pack/plugins/security_solution/public/resolver/view/use_camera.ts +++ b/x-pack/plugins/security_solution/public/resolver/view/use_camera.ts @@ -281,17 +281,61 @@ export function useCamera(): { * handle that. */ export function useAutoUpdatingClientRect(): [DOMRect | null, (node: Element | null) => void] { + // This hooks returns `rect`. const [rect, setRect] = useState(null); - // Using state as ref.current update does not trigger effect hook when reset + + const { ResizeObserver, requestAnimationFrame } = useContext(SideEffectContext); + + // Keep the current DOM node in state so that we can create a ResizeObserver for it via `useEffect`. const [currentNode, setCurrentNode] = useState(null); + // `ref` will be used with a react element. When the element is available, this function will be called. const ref = useCallback((node: Element | null) => { + // track the node in state setCurrentNode(node); - if (node !== null) { - setRect(node.getBoundingClientRect()); - } }, []); - const { ResizeObserver } = useContext(SideEffectContext); + + /** + * Any time the DOM node changes (to something other than `null`) recalculate the DOMRect and set it (which will cause it to be returned from the hook. + * This effect re-runs when the DOM node has changed. + */ + useEffect(() => { + if (currentNode !== null) { + // When the DOM node is received, immedaiately calculate its DOM Rect and return that + setRect(currentNode.getBoundingClientRect()); + } + }, [currentNode]); + + /** + * When scroll events occur, recalculate the DOMRect. DOMRect represents the position of an element relative to the viewport, so that may change during scroll (depending on the layout.) + * This effect re-runs when the DOM node has changed. + */ + useEffect(() => { + // the last scrollX and scrollY values that we handled + let previousX: number = window.scrollX; + let previousY: number = window.scrollY; + + const handleScroll = () => { + requestAnimationFrame(() => { + // synchronously read from the DOM + const currentX = window.scrollX; + const currentY = window.scrollY; + + if (currentNode !== null && (previousX !== currentX || previousY !== currentY)) { + setRect(currentNode.getBoundingClientRect()); + } + + previousX = currentX; + previousY = currentY; + }); + }; + + window.addEventListener('scroll', handleScroll, { passive: true }); + return () => { + window.removeEventListener('scroll', handleScroll); + }; + }, [currentNode, requestAnimationFrame]); + useEffect(() => { if (currentNode !== null) { const resizeObserver = new ResizeObserver((entries) => {