Last active
October 27, 2022 00:42
-
-
Save kristersd/f37745431f1859b624e8794627e3564c to your computer and use it in GitHub Desktop.
Handles mouse and scroll event position relative to container
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useEffect, useLayoutEffect, useRef, useState } from "react"; | |
import "./styles.css"; | |
const clamp = (number: number, min: number, max: number) => { | |
return Math.min(Math.max(number, min), max); | |
}; | |
export default function App() { | |
const [{ x, y }, setMousePosition] = useState({ x: 0, y: 0 }); | |
const containerRef = useRef<HTMLDivElement>(null); | |
const scrollTopPositionRef = useRef<number>(0); | |
const [sidebarHeight, setSidebarHeight] = useState(0); | |
useLayoutEffect(() => { | |
const sidebarHeight = | |
document.querySelector(".sidebar__header")?.getBoundingClientRect() | |
.height || 0; | |
setSidebarHeight(sidebarHeight); | |
setMousePosition({ x: 0, y: sidebarHeight }); | |
}, []); | |
useEffect(() => { | |
const element = containerRef.current; | |
if (!element) { | |
return; | |
} | |
const initialTop = element.getBoundingClientRect().top; | |
const initialLeft = element.getBoundingClientRect().left; | |
const handler = (e: MouseEvent | Event) => { | |
if (!e) { | |
return; | |
} | |
if (e.type === "scroll") { | |
setMousePosition((prevState) => ({ | |
x: prevState.x, | |
y: clamp( | |
prevState.y + | |
(scrollTopPositionRef.current - element.scrollTop) * -1, | |
sidebarHeight, | |
Infinity | |
) | |
})); | |
scrollTopPositionRef.current = element.scrollTop; | |
return; | |
} | |
setMousePosition({ | |
x: (e as MouseEvent).pageX - initialLeft, | |
y: clamp( | |
(e as MouseEvent).pageY - initialTop + element.scrollTop, | |
sidebarHeight, | |
Infinity | |
) | |
}); | |
}; | |
const eventTypes = ["scroll", "mousemove"]; | |
for (const eventType of eventTypes) { | |
element.addEventListener(eventType, handler); | |
} | |
return () => { | |
for (const eventType of eventTypes) { | |
element.removeEventListener(eventType, handler); | |
} | |
}; | |
}, [sidebarHeight]); | |
return ( | |
<div className="App"> | |
<div ref={containerRef} className="container"> | |
<div className="sidebar"> | |
<div | |
className="sidebar__header" | |
style={{ backgroundColor: "purple" }} | |
> | |
Sidebar Sidebar Sidebar Sidebar Sidebar Sidebar Sidebar Sidebar | |
Sidebar Sidebar | |
</div> | |
<div className="sidebar__position" style={{ top: y }}> | |
X: {x}, Y: {y - sidebarHeight} | |
</div> | |
</div> | |
<div className="content"> | |
<div className="content__main">Main content</div> | |
</div> | |
</div> | |
</div> | |
); | |
} | |
// Play around - https://codesandbox.io/s/react-typescript-forked-wszqm8?file=/src/App.tsx:0-2587 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment