/*** 滚动 / 视口监听:根节点被移出文档后自动摘净监听,避免单页应用里反复挂载累积监听 ***/ export function watchViewport(root: HTMLElement, host: HTMLElement | Window, handler: () => void): void { // —— 状态集中声明 —— const run = (): void => { if (!root.isConnected) { // 已卸载:一次性摘掉 scroll 与 resize,后续不再进 handler host.removeEventListener('scroll', run); window.removeEventListener('resize', run); return; } handler(); }; host.addEventListener('scroll', run, { passive: true }); // 被动监听:不阻塞滚动 window.addEventListener('resize', run); // 视口变化会改判定线与可滚距离 handler(); // 首帧同步一次:刷新时停在页面中段也能得到正确状态 } /*** 尺寸监听:根节点尺寸变化时重算(视口缩放、侧栏展开、字体变大),脱离文档即自断,不用手动销毁 ***/ export function trackSize(root: HTMLElement, handler: () => void): void { if (typeof ResizeObserver === 'undefined') { window.addEventListener('resize', handler); return; } // 老环境回落:至少跟上视口变化 const ro = new ResizeObserver(() => { if (!root.isConnected) { ro.disconnect(); return; } // 已卸载:断开观察,避免闭包被观察器拽住 handler(); }); ro.observe(root); }