Skip to content

Instantly share code, notes, and snippets.

@brysgo
Created April 27, 2021 16:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brysgo/75a62c40e72fe85779e2aa37432d69cc to your computer and use it in GitHub Desktop.
Save brysgo/75a62c40e72fe85779e2aa37432d69cc to your computer and use it in GitHub Desktop.
cascading sticky elements (open sourced from a side project of mine)
import useComponentSize from '@rehooks/component-size'
import useWindowScrollPosition, { WindowScrollPosition } from '@rehooks/window-scroll-position'
import { useMemo, useRef, RefObject } from 'react'
interface Props<T extends HTMLElement> {
min?: number
max?: number
ref?: RefObject<T>
}
export type PeekabooResult<T extends HTMLElement> = {
top: number
position: WindowScrollPosition
show: (...offsets: number[]) => void
offset: number
collapsed: boolean
min?: number
max: number
ref: RefObject<T>
height: number
}
export function usePeekaboo<T extends HTMLElement = HTMLElement>({ min, max = 0, ref: aRef }: Props<T>) {
const newRef = useRef<T>(null)
const ref: RefObject<T> = aRef || newRef
let { height } = useComponentSize<T>(ref)
const positionCoords: WindowScrollPosition = useWindowScrollPosition({ throttle: 0 })
const position = positionCoords.y
const scrollInfo = useMemo(() => ({ root: { lastTop: Infinity, lastPosition: Infinity, lastHeight: height } }), [height])
const { lastPosition, lastTop } = scrollInfo.root
const deltaOrMax = Math.min(lastTop + (lastPosition - position), max)
const top = Math.max(deltaOrMax, min || -height || -Infinity)
scrollInfo.root.lastTop = top
scrollInfo.root.lastPosition = position
const offset = top - max
const show = (...otherOffsets: number[]) => {
window.scrollTo(positionCoords.x, positionCoords.y + offset + otherOffsets.reduce((s, x) => s + x, 0))
}
const collapsed = top < max
return { top, position: positionCoords, show, offset, collapsed, min, max, ref, height }
}
import { RefObject } from 'react'
import { usePeekaboo, PeekabooResult } from 'hooks/usePeekaboo'
interface Params<T extends HTMLElement> {
max: number
ref?: RefObject<T>
}
export function useStackedPeekaboo<T extends HTMLElement = HTMLElement, P extends HTMLElement = HTMLElement>(
parentPeekaboo: PeekabooResult<P>,
{ max, ref }: Params<T>
) {
const result = usePeekaboo<T>({
max: parentPeekaboo.top + parentPeekaboo.height - max,
ref
})
const show = () => {
return result.show(parentPeekaboo.offset)
}
return { ...result, show }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment