diff --git a/web/src/components/UsageHeatMap.tsx b/web/src/components/UsageHeatMap.tsx index f0752139..4b621a50 100644 --- a/web/src/components/UsageHeatMap.tsx +++ b/web/src/components/UsageHeatMap.tsx @@ -59,9 +59,9 @@ const UsageHeatMap: React.FC = () => { return; } - const targetEl = event.target as HTMLElement; - popupRef.current.style.left = targetEl.offsetLeft + "px"; - popupRef.current.style.top = targetEl.offsetTop - 4 + "px"; + const bounding = utils.getElementBounding(event.target as HTMLElement); + popupRef.current.style.left = bounding.left + "px"; + popupRef.current.style.top = bounding.top - 4 + "px"; }, []); const handleUsageStatItemMouseLeave = useCallback(() => { diff --git a/web/src/helpers/utils.ts b/web/src/helpers/utils.ts index 94f3fd9c..49a522fa 100644 --- a/web/src/helpers/utils.ts +++ b/web/src/helpers/utils.ts @@ -1,3 +1,5 @@ +import { assign } from "lodash-es"; + export function getNowTimeStamp(): number { return Date.now(); } @@ -217,3 +219,52 @@ export function getImageSize(src: string): Promise<{ width: number; height: numb imgEl.remove(); }); } + +export const getElementBounding = (element: HTMLElement, relativeEl?: HTMLElement) => { + const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; + const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft; + + relativeEl = relativeEl || document.body; + + const elementRect = element.getBoundingClientRect(); + const relativeElRect = relativeEl.getBoundingClientRect(); + const relativeElPosition = window.getComputedStyle(relativeEl).getPropertyValue("position"); + + const bounding = { + width: elementRect.width, + height: elementRect.height, + }; + + if ((relativeEl.tagName !== "BODY" && relativeElPosition === "relative") || relativeElPosition === "sticky") { + return assign(bounding, { + top: elementRect.top - relativeElRect.top, + left: elementRect.left - relativeElRect.left, + }); + } + + const isElementFixed = (element: HTMLElement): boolean => { + const parentNode = element.parentNode; + + if (!parentNode || parentNode.nodeName === "HTML") { + return false; + } + + if (window.getComputedStyle(element).getPropertyValue("position") === "fixed") { + return true; + } + + return isElementFixed(parentNode as HTMLElement); + }; + + if (isElementFixed(element)) { + return assign(bounding, { + top: elementRect.top, + left: elementRect.left, + }); + } + + return assign(bounding, { + top: elementRect.top + scrollTop, + left: elementRect.left + scrollLeft, + }); +}; diff --git a/web/src/less/usage-heat-map.less b/web/src/less/usage-heat-map.less index 64db16e9..0fdf08d5 100644 --- a/web/src/less/usage-heat-map.less +++ b/web/src/less/usage-heat-map.less @@ -58,7 +58,7 @@ } > .usage-detail-container { - @apply absolute left-0 top-0 ml-2 -mt-9 p-2 -translate-x-1/2 select-none text-white text-xs rounded whitespace-nowrap; + @apply fixed left-0 top-0 ml-2 -mt-9 p-2 -translate-x-1/2 select-none text-white text-xs rounded whitespace-nowrap; background-color: rgba(0, 0, 0, 0.8); z-index: 2;