iter(g58): 新增 affix 固钉组件
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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 }; // 再导出常用内核类型,方便调用方
|
||||
|
||||
@@ -68,7 +68,8 @@
|
||||
"colorPicker(props):颜色选择,预设色板(缺省语义令牌色)+ 原生取色器 + 手输 CSS 颜色串,value 为颜色串信号",
|
||||
"tour(props):引导漫游,返回 { el, start, close } 句柄;聚光框按 target 选择器定位,卡片自动选方位,Esc / 跳过走 onClose",
|
||||
"watermark(props, ...children):内容容器 + 平铺水印覆盖层,canvas 生成单元、颜色取自令牌,覆盖层不挡交互",
|
||||
"checkboxGroup(props):多选组,value 为数组信号,min/max 用禁用边界项实现(不抛错),checkAll 支持半选且只统计未禁用项"
|
||||
"checkboxGroup(props):多选组,value 为数组信号,min/max 用禁用边界项实现(不抛错),checkAll 支持半选且只统计未禁用项",
|
||||
"affix(props, ...children):固钉,滚过 offset 后内容转 fixed 贴顶、外层保留占位高度,固定后宽度随容器自适应"
|
||||
],
|
||||
"keyTypes": [
|
||||
"ButtonProps",
|
||||
@@ -2390,6 +2391,35 @@
|
||||
"checkAll 行的选中/半选只统计未禁用项,禁用项不计入基数",
|
||||
"direction 传 vertical 改为纵向排列;disabled 支持信号,整组响应式禁用"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "affix",
|
||||
"title": "固钉 Affix",
|
||||
"demo": "sec-affix",
|
||||
"props": [
|
||||
{
|
||||
"n": "offset",
|
||||
"t": "number",
|
||||
"d": "距视口顶部多少像素后转为固定;缺省 0"
|
||||
},
|
||||
{
|
||||
"n": "target",
|
||||
"t": "HTMLElement | Window",
|
||||
"d": "监听的滚动容器;缺省 window(整页)"
|
||||
},
|
||||
{
|
||||
"n": "children",
|
||||
"t": "...unknown[]",
|
||||
"d": "被固定的内容,作为第二个及之后的实参传入"
|
||||
}
|
||||
],
|
||||
"example": "affix({ offset: 8 }, card({ title: '滚动时我会贴顶' }, h('p', null, '正文')))",
|
||||
"steps": [
|
||||
"把要贴住的内容作为实参传入;组件内部包两层:外层占位、内层负责 fixed",
|
||||
"滚动到 offset 以内(默认贴视口顶)时内层转 fixed,外层同步撑出等高占位,页面不会塌跳",
|
||||
"固定期间宽度与横向位置跟随占位容器,窗口缩放或父容器变宽都自动贴合",
|
||||
"内滚容器场景把该元素传给 target,判定改为基于它的滚动位置"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3250,3 +3250,10 @@ button.ua-step-dot:hover {
|
||||
align-items: flex-start;
|
||||
gap: var(--ua-space-1);
|
||||
}
|
||||
.ua-affix {
|
||||
position: relative;
|
||||
}
|
||||
.ua-affix-inner.is-fixed {
|
||||
position: fixed;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user