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 全绿",
|
"acceptance": "TabItem 新增 closable(缺省不可关),渲染 × 关闭按钮(aria-label=关闭 <label>),点击或页签聚焦后按 Delete 触发 onClose(value);指示条改为量外壳宽度,可关闭页签的高亮能盖住 ×;触摸设备(hover: none)常显 ×;表单化入参不变(closable 在项上),文档参数表/手把手/示例与演示(三张可关订单页签,关完显示空态)同步;npm run check 全绿",
|
||||||
"notes": "自补:组件可用性;关闭只回调不改数据,符合受控约定",
|
"notes": "自补:组件可用性;关闭只回调不改数据,符合受控约定",
|
||||||
"status": "done"
|
"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 { h } from '@ui-arch/frame';
|
||||||
import type { AffixProps } from './types.js';
|
import type { AffixProps } from './types.js';
|
||||||
|
import { watchViewport } from './watch.js';
|
||||||
|
|
||||||
/*** 固钉:内容滚动越过阈值后转 fixed 贴住视口顶部,外层保留占位高度避免布局跳动 ***/
|
/*** 固钉:内容滚动越过阈值后转 fixed 贴住视口顶部,外层保留占位高度避免布局跳动 ***/
|
||||||
export function affix(props: AffixProps, ...children: unknown[]): HTMLElement {
|
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 }); // 被动监听,不阻塞滚动
|
watchViewport(wrap, scroller, sync); // 滚动 / 缩放后重算,节点脱离文档即自摘监听
|
||||||
window.addEventListener('resize', sync); // 视口变化会改判定与宽度
|
|
||||||
sync(); // 首屏同步一次:刷新时若已滚动到位,直接进入固定态
|
|
||||||
return wrap;
|
return wrap;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { h } from '@ui-arch/frame';
|
import { h } from '@ui-arch/frame';
|
||||||
import type { AnchorItem, AnchorProps } from './types.js';
|
import type { AnchorItem, AnchorProps } from './types.js';
|
||||||
|
import { watchViewport } from './watch.js';
|
||||||
|
|
||||||
/*** 锚点导航:竖排区块链接,滚动时高亮当前区块,点击平滑跳转(尊重减少动效偏好) ***/
|
/*** 锚点导航:竖排区块链接,滚动时高亮当前区块,点击平滑跳转(尊重减少动效偏好) ***/
|
||||||
export function anchor(props: AnchorProps): HTMLElement {
|
export function anchor(props: AnchorProps): HTMLElement {
|
||||||
@@ -70,8 +71,6 @@ export function anchor(props: AnchorProps): HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const host: HTMLElement | Window = scroller ?? window;
|
const host: HTMLElement | Window = scroller ?? window;
|
||||||
host.addEventListener('scroll', sync, { passive: true }); // 被动监听:不阻塞滚动
|
watchViewport(root, host, sync); // 滚动 / 缩放后同步高亮,节点脱离文档即自摘监听
|
||||||
window.addEventListener('resize', sync); // 视口变化会改变判定线
|
|
||||||
sync(); // 首屏同步一次,刷新后停在中间位置也能高亮正确项
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { h } from '@ui-arch/frame';
|
import { h } from '@ui-arch/frame';
|
||||||
import type { BackTopProps } from './types.js';
|
import type { BackTopProps } from './types.js';
|
||||||
|
import { watchViewport } from './watch.js';
|
||||||
|
|
||||||
/*** 返回顶部:滚动越过阈值浮现,环形进度显示已滚比例,点击平滑回顶(尊重减少动效偏好) ***/
|
/*** 返回顶部:滚动越过阈值浮现,环形进度显示已滚比例,点击平滑回顶(尊重减少动效偏好) ***/
|
||||||
export function backTop(props: BackTopProps = {}): HTMLElement {
|
export function backTop(props: BackTopProps = {}): HTMLElement {
|
||||||
@@ -39,8 +40,6 @@ export function backTop(props: BackTopProps = {}): HTMLElement {
|
|||||||
props.onClick?.();
|
props.onClick?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
scroller.addEventListener('scroll', sync, { passive: true }); // 被动监听:不阻塞滚动
|
watchViewport(btn, scroller, sync); // 滚动 / 缩放后同步浮现态,节点脱离文档即自摘监听
|
||||||
window.addEventListener('resize', sync); // 视口变化会改可滚距离
|
|
||||||
sync(); // 首屏同步一次,避免刷新后停在中间位置却未浮现
|
|
||||||
return btn;
|
return btn;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { Signal } from '@ui-arch/frame';
|
|||||||
import { pushOverlay } from './overlay.js';
|
import { pushOverlay } from './overlay.js';
|
||||||
import { button } from './button.js';
|
import { button } from './button.js';
|
||||||
import type { TourHandle, TourProps } from './types.js';
|
import type { TourHandle, TourProps } from './types.js';
|
||||||
|
import { watchViewport } from './watch.js';
|
||||||
|
|
||||||
/*** 引导漫游:聚光框 + 说明卡逐步指引,聚光用超大 box-shadow 挖洞省掉遮罩层,Esc / 跳过可退出 ***/
|
/*** 引导漫游:聚光框 + 说明卡逐步指引,聚光用超大 box-shadow 挖洞省掉遮罩层,Esc / 跳过可退出 ***/
|
||||||
export function tour(props: TourProps): TourHandle {
|
export function tour(props: TourProps): TourHandle {
|
||||||
@@ -84,8 +85,7 @@ export function tour(props: TourProps): TourHandle {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onViewport = (): void => { if (idx() >= 0) place(); }; // 滚动/缩放后重算位置
|
const onViewport = (): void => { if (idx() >= 0) place(); }; // 滚动/缩放后重算位置
|
||||||
window.addEventListener('resize', onViewport);
|
watchViewport(root, window, onViewport); // 节点脱离文档即自摘监听:引导层反复挂载也不累积
|
||||||
window.addEventListener('scroll', onViewport, { passive: true });
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
el: root,
|
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