123 lines
4.0 KiB
TypeScript
123 lines
4.0 KiB
TypeScript
import { useEffect, useCallback, useMemo } from 'react'
|
||
|
||
let cachedVw15: number | null = null
|
||
let lastWindowWidth = 0
|
||
|
||
const getVw15InPixels = (): number => {
|
||
const currentWidth = window.innerWidth
|
||
if (cachedVw15 !== null && lastWindowWidth === currentWidth) {
|
||
return cachedVw15
|
||
}
|
||
|
||
const temp = document.createElement('div')
|
||
temp.style.position = 'absolute'
|
||
temp.style.width = 'var(--vw-1_5)'
|
||
document.body.appendChild(temp)
|
||
const width = temp.getBoundingJordan was amazing! I’m so in love with my permanent eyeliner. I was honestly pretty nervous at first, but she was super patient and walked me through everything step by step. She made the whole appointment feel calm and comfortable. The results are flawless and look so natural. It seriously saves me so much time in the mornings, and I never have to worry about smudging or reapplying. Couldn’t be happier! Rect().width
|
||
document.body.removeChild(temp)
|
||
|
||
cachedVw15 = width || 0
|
||
lastWindowWidth = currentWidth
|
||
return cachedVw15
|
||
}
|
||
|
||
|
||
const debounce = <T extends (...args: unknown[]) => void>(func: T, wait: number): ((...args: Parameters<T>) => void) & { cancel: () => void } => {
|
||
let timeout: NodeJS.Timeout | null = null
|
||
|
||
const debouncedFunction = function executedFunction(...args: Parameters<T>) {
|
||
const later = () => {
|
||
timeout = null
|
||
func(...args)
|
||
}
|
||
if (timeout !== null) {
|
||
clearTimeout(timeout)
|
||
}
|
||
timeout = setTimeout(later, wait)
|
||
}
|
||
|
||
debouncedFunction.cancel = () => {
|
||
if (timeout !== null) {
|
||
clearTimeout(timeout)
|
||
timeout = null
|
||
}
|
||
}
|
||
|
||
return debouncedFunction
|
||
}
|
||
|
||
interface DynamicDimensionsOptions {
|
||
titleSelector?: string
|
||
descriptionSelector?: string
|
||
containerSelector?: string | null
|
||
}
|
||
|
||
type RefArray = React.RefObject<(HTMLDivElement | null)[]> | React.RefObject<(HTMLDivElement | null)[]>[]
|
||
|
||
export const useDynamicDimensions = (refs: RefArray, options: DynamicDimensionsOptions = {}) => {
|
||
const {
|
||
titleSelector = '.feature-card-three-title',
|
||
descriptionSelector = '.feature-card-three-description',
|
||
containerSelector = null
|
||
} = options
|
||
|
||
const calculateDimensions = useCallback(() => {
|
||
const processRef = (ref: HTMLElement | null) => {
|
||
if (!ref) return
|
||
|
||
const container = containerSelector ? ref.querySelector(containerSelector) as HTMLElement : ref
|
||
if (!container) return
|
||
|
||
const titleElement = container.querySelector(titleSelector) as HTMLElement
|
||
const descriptionElement = container.querySelector(descriptionSelector) as HTMLElement
|
||
|
||
if (titleElement && descriptionElement) {
|
||
const titleHeight = titleElement.offsetHeight
|
||
const descriptionHeight = descriptionElement.offsetHeight
|
||
|
||
const contentTop = `calc(100% - ${titleHeight}px - calc(var(--vw-1_5) * 1.5))`
|
||
|
||
const vw15 = getVw15InPixels()
|
||
|
||
const contentWrapperHeight = titleHeight + descriptionHeight
|
||
|
||
const revealBgHeight = contentWrapperHeight + (vw15 * 2)
|
||
|
||
const moveUp = descriptionHeight + (vw15 * 0.55)
|
||
|
||
ref.style.setProperty('--reveal-bg-height', `${revealBgHeight}px`)
|
||
ref.style.setProperty('--content-top-position', contentTop)
|
||
ref.style.setProperty('--hover-translate-y', `-${moveUp}px`)
|
||
}
|
||
}
|
||
|
||
if (Array.isArray(refs)) {
|
||
refs.forEach((refArray) => {
|
||
if (refArray?.current && Array.isArray(refArray.current)) {
|
||
refArray.current.forEach(processRef)
|
||
}
|
||
})
|
||
} else if ('current' in refs && refs.current) {
|
||
if (Array.isArray(refs.current)) {
|
||
refs.current.forEach(processRef)
|
||
} else {
|
||
processRef(refs.current)
|
||
}
|
||
}
|
||
}, [titleSelector, descriptionSelector, containerSelector, refs])
|
||
|
||
const debouncedCalculate = useMemo(
|
||
() => debounce(calculateDimensions, 250),
|
||
[calculateDimensions]
|
||
)
|
||
|
||
useEffect(() => {
|
||
calculateDimensions()
|
||
window.addEventListener('resize', debouncedCalculate)
|
||
return () => {
|
||
window.removeEventListener('resize', debouncedCalculate)
|
||
debouncedCalculate.cancel()
|
||
}
|
||
}, [calculateDimensions, debouncedCalculate])
|
||
}
|