iter(g58): 新增 affix 固钉组件

This commit is contained in:
HIsheR
2026-10-26 12:23:25 +08:00
parent 89cddada21
commit 7684ae2b10
12 changed files with 166 additions and 10 deletions
+43
View File
@@ -0,0 +1,43 @@
import { h } from '@ui-arch/frame';
import type { AffixProps } from './types.js';
/*** 固钉:内容滚动越过阈值后转 fixed 贴住视口顶部,外层保留占位高度避免布局跳动 ***/
export function affix(props: AffixProps, ...children: unknown[]): HTMLElement {
// —— 状态集中声明 ——
const offset = props.offset ?? 0; // 触发固定的阈值
const scroller = props.target ?? window; // 滚动容器
const inner = h('div', { class: 'ua-affix-inner' }, ...children) as HTMLElement; // 真正会被固定的内容
const wrap = h('div', { class: 'ua-affix' }, inner) as HTMLElement; // 占位容器,固定时撑住原位
let fixed = false; // 当前是否已固定,避免每次滚动都重写样式
/*** 同步固定态:越过阈值即 fixed + 撑占位高度,回到阈值以上就还原 ***/
const sync = (): void => {
const box = wrap.getBoundingClientRect(); // 占位容器几何:left / width 决定固定后的横向位置
const should = box.top <= offset; // 已滚到阈值之上
if (should === fixed) {
if (fixed) { // 固定中:宽度与横向位置跟随容器(窗口缩放 / 父容器变宽)
inner.style.width = `${box.width}px`;
inner.style.left = `${box.left}px`;
}
return; // 卫语句:状态未变不重写其余样式
}
fixed = should;
if (fixed) {
wrap.style.height = `${inner.offsetHeight}px`; // 先撑住占位,再让 inner 脱离文档流
inner.classList.add('is-fixed');
inner.style.top = `${offset}px`;
inner.style.left = `${box.left}px`;
inner.style.width = `${box.width}px`;
} else {
inner.classList.remove('is-fixed');
inner.style.top = '';
inner.style.width = '';
wrap.style.height = '';
}
};
scroller.addEventListener('scroll', sync, { passive: true }); // 被动监听,不阻塞滚动
window.addEventListener('resize', sync); // 视口变化会改判定与宽度
sync(); // 首屏同步一次:刷新时若已滚动到位,直接进入固定态
return wrap;
}
+1
View File
@@ -54,3 +54,4 @@ export { backTop } from './backTop.js';
export { colorPicker } from './colorPicker.js';
export { tour } from './tour.js';
export { watermark } from './watermark.js';
export { affix } from './affix.js';
+6
View File
@@ -620,4 +620,10 @@ export interface CheckboxGroupProps {
onChange?: (values: string[]) => void; // 变更回调(传副本)
}
/*** 固钉入参 ***/
export interface AffixProps {
offset?: number; // 距视口顶部多少像素后转为固定;缺省 0
target?: HTMLElement | Window; // 监听的滚动容器;缺省 window
}
export type { ColumnDef, RowKey }; // 再导出常用内核类型,方便调用方