function getStickyRoot(node: Element) { let current = node.parentElement; while (current && current !== document.body) { const computedStyle = window.getComputedStyle(current); const overflow = computedStyle.getPropertyValue('overflow'); if (overflow === 'scroll' || overflow === 'auto') { return current; } current = current.parentElement; } return document.body; } function isNumber(value: any): value is number { // the isFinite() check also suffices for !isNaN() return typeof value === 'number' && Number.isFinite(value); } export function stuck(node: Element) { const root = getStickyRoot(node); function detectStuck() { if (getComputedStyle(node).position !== 'sticky') { const dirs = ['', 't', 'r', 'b', 'l', 'x', 'y']; for (const dir of dirs) { const suffix = dir ? `-${dir}` : ''; node.classList.remove(`stuck${suffix}`); } } const rect = node.getBoundingClientRect(); const rootRect = root.getBoundingClientRect(); const computedStyle = window.getComputedStyle(node); const top = parseFloat(computedStyle.getPropertyValue('top')); const right = parseFloat(computedStyle.getPropertyValue('right')); const bottom = parseFloat(computedStyle.getPropertyValue('bottom')); const left = parseFloat(computedStyle.getPropertyValue('left')); const t = isNumber(top) && rect.top - rootRect.top === top; const r = isNumber(right) && rootRect.right - rect.right === right; const b = isNumber(bottom) && rootRect.bottom - rect.bottom === bottom; const l = isNumber(left) && rect.left - rootRect.left === left; const x = l || r; const y = t || b; let isStuck: boolean = false; for (const [dir, condition] of Object.entries({ t, r, b, l, x, y })) { if (condition) { node.classList.add(`stuck-${dir}`); isStuck = true; } else { node.classList.remove(`stuck-${dir}`); } } if (isStuck) { node.classList.add('stuck'); } else { node.classList.remove('stuck'); } } root.addEventListener('scroll', detectStuck, { passive: true }); window.addEventListener('resize', detectStuck, { passive: true }); return { destroy() { root.removeEventListener('scroll', detectStuck); window.removeEventListener('resize', detectStuck); }, }; }