iter(g106): 滚动监听改为卸载自清理
This commit is contained in:
@@ -1204,6 +1204,14 @@
|
||||
"acceptance": "TabItem 新增 closable(缺省不可关),渲染 × 关闭按钮(aria-label=关闭 <label>),点击或页签聚焦后按 Delete 触发 onClose(value);指示条改为量外壳宽度,可关闭页签的高亮能盖住 ×;触摸设备(hover: none)常显 ×;表单化入参不变(closable 在项上),文档参数表/手把手/示例与演示(三张可关订单页签,关完显示空态)同步;npm run check 全绿",
|
||||
"notes": "自补:组件可用性;关闭只回调不改数据,符合受控约定",
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"id": "g106",
|
||||
"title": "滚动监听改为卸载自清理",
|
||||
"motivation": "affix / backTop / anchor / tour 四个组件都是「挂 window 或容器的 scroll+resize 监听,从不摘」:单页应用里每次进页面重建组件就多一根监听,旧闭包连同旧 DOM 一起被 window 拽住,切十次页面就十份空转的 sync 在滚动时跑",
|
||||
"acceptance": "新增 watch.ts 的 watchViewport(root, host, handler):滚动 / 缩放时若 root 已脱离文档就一次性摘掉两个监听并停止回调,否则转交 handler 并首帧同步一次;四个组件改用该助手,行为与原来一致(首帧同步保留);npm run check 全绿",
|
||||
"notes": "自补:一致性与泄漏治理;一处实现四处复用,后续新增长期监听组件照抄",
|
||||
"status": "open"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(); // 首帧同步一次:刷新时停在页面中段也能得到正确状态
|
||||
}
|
||||
Reference in New Issue
Block a user