Skip to content

Instantly share code, notes, and snippets.

@medatech
Created May 14, 2020 11:15
Show Gist options
  • Save medatech/500a281c11ce754921d2948cee426c15 to your computer and use it in GitHub Desktop.
Save medatech/500a281c11ce754921d2948cee426c15 to your computer and use it in GitHub Desktop.
Ensure that an element is always on screen
import { useEffect } from "react"
/**
* This hook takes an element and ensures that it does not go outside the bounds of the viewport. It is useful
* for things like a context menu where you don't want the menu to appear off screen.
*
* The only requriements for the DOM reference passed in is that it is absolutely positioned.
* To prevent it flickering in the original place and then moving with the correction, set the visibility CSS attribute
* of the DOM element to 'hidden'. This hook will set it to 'visible' when it has computed the corrected position
*
* @param {Reference} ref The React reference for the DOM element to ensure is on screen.
* @param {Number} padding The amount of space to leave around the edge of the screen
**/
export default function useEnsureOnScreen(ref, padding = 2) {
useEffect(() => {
if (ref.current === null) return
const bounds = ref.current.getBoundingClientRect()
const measurement = {
viewportWidth:
ref.current.ownerDocument.documentElement.clientWidth,
viewportHeight:
ref.current.ownerDocument.documentElement.clientHeight,
left: bounds.left,
right: bounds.right,
top: bounds.top,
bottom: bounds.bottom,
width: bounds.width,
height: bounds.height,
}
if (measurement.right > measurement.viewportWidth) {
// It's too far to the right, so bring it left
const newLeft =
measurement.viewportWidth - measurement.width - padding
ref.current.style.left = `${newLeft}px`
}
if (measurement.left < 0) {
ref.current.style.left = `${padding}px`
}
if (measurement.bottom > measurement.viewportHeight) {
// It's too far off the bottom, so bring it top
const newTop =
measurement.viewportHeight - measurement.height - padding
ref.current.style.top = `${newTop}px`
}
if (measurement.top < 0) {
ref.current.style.top = `${padding}px`
}
// Now we can show it
ref.current.style.visibility = `visible`
}, [ref.current, padding])
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment