iter(g106): 滚动监听改为卸载自清理

This commit is contained in:
HIsheR
2026-10-26 13:58:22 +08:00
parent 7d57f261df
commit c66bcf35da
6 changed files with 31 additions and 11 deletions
+2 -3
View File
@@ -1,5 +1,6 @@
import { h } from '@ui-arch/frame';
import type { AffixProps } from './types.js';
import { watchViewport } from './watch.js';
/*** 固钉:内容滚动越过阈值后转 fixed 贴住视口顶部,外层保留占位高度避免布局跳动 ***/
export function affix(props: AffixProps, ...children: unknown[]): HTMLElement {
@@ -36,8 +37,6 @@ export function affix(props: AffixProps, ...children: unknown[]): HTMLElement {
}
};
scroller.addEventListener('scroll', sync, { passive: true }); // 被动监听,不阻塞滚动
window.addEventListener('resize', sync); // 视口变化会改判定与宽度
sync(); // 首屏同步一次:刷新时若已滚动到位,直接进入固定态
watchViewport(wrap, scroller, sync); // 滚动 / 缩放后重算,节点脱离文档即自摘监听
return wrap;
}
+2 -3
View File
@@ -1,5 +1,6 @@
import { h } from '@ui-arch/frame';
import type { AnchorItem, AnchorProps } from './types.js';
import { watchViewport } from './watch.js';
/*** 锚点导航:竖排区块链接,滚动时高亮当前区块,点击平滑跳转(尊重减少动效偏好) ***/
export function anchor(props: AnchorProps): HTMLElement {
@@ -70,8 +71,6 @@ export function anchor(props: AnchorProps): HTMLElement {
}
const host: HTMLElement | Window = scroller ?? window;
host.addEventListener('scroll', sync, { passive: true }); // 被动监听:不阻塞滚动
window.addEventListener('resize', sync); // 视口变化会改变判定线
sync(); // 首屏同步一次,刷新后停在中间位置也能高亮正确项
watchViewport(root, host, sync); // 滚动 / 缩放后同步高亮,节点脱离文档即自摘监听
return root;
}
+2 -3
View File
@@ -1,5 +1,6 @@
import { h } from '@ui-arch/frame';
import type { BackTopProps } from './types.js';
import { watchViewport } from './watch.js';
/*** 返回顶部:滚动越过阈值浮现,环形进度显示已滚比例,点击平滑回顶(尊重减少动效偏好) ***/
export function backTop(props: BackTopProps = {}): HTMLElement {
@@ -39,8 +40,6 @@ export function backTop(props: BackTopProps = {}): HTMLElement {
props.onClick?.();
};
scroller.addEventListener('scroll', sync, { passive: true }); // 被动监听:不阻塞滚动
window.addEventListener('resize', sync); // 视口变化会改可滚距离
sync(); // 首屏同步一次,避免刷新后停在中间位置却未浮现
watchViewport(btn, scroller, sync); // 滚动 / 缩放后同步浮现态,节点脱离文档即自摘监听
return btn;
}
+2 -2
View File
@@ -3,6 +3,7 @@ import type { Signal } from '@ui-arch/frame';
import { pushOverlay } from './overlay.js';
import { button } from './button.js';
import type { TourHandle, TourProps } from './types.js';
import { watchViewport } from './watch.js';
/*** 引导漫游:聚光框 + 说明卡逐步指引,聚光用超大 box-shadow 挖洞省掉遮罩层,Esc / 跳过可退出 ***/
export function tour(props: TourProps): TourHandle {
@@ -84,8 +85,7 @@ export function tour(props: TourProps): TourHandle {
};
const onViewport = (): void => { if (idx() >= 0) place(); }; // 滚动/缩放后重算位置
window.addEventListener('resize', onViewport);
window.addEventListener('scroll', onViewport, { passive: true });
watchViewport(root, window, onViewport); // 节点脱离文档即自摘监听:引导层反复挂载也不累积
return {
el: root,
+15
View File
@@ -0,0 +1,15 @@
/*** 滚动 / 视口监听:根节点被移出文档后自动摘净监听,避免单页应用里反复挂载累积监听 ***/
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(); // 首帧同步一次:刷新时停在页面中段也能得到正确状态
}