fix(gxnlpt): 修复导航栏固定定位异常
根因: _measureStickyGeometry 在任意 scroll 位置测量 sidebarInitialTop,未归一化到 scroll 0,导致 keep-alive 页面返回或点击靠后分类后 sidebarInitialTop 错误,sticky 被钳到视口外消失并产生顶部空白 修复: 在测量 sidebarInitialTop 时加上当前 scrollTop 修正值,确保始终为 scroll 0 时的正确基准位置 同时补全 scheduleScrollUnlock 的 scrollend 兼容性轮询兜底,消除分类高亮在滚动动画中被错误更新的问题 .sidebar 补 min-height 约束防止 portal 后容器高度塌缩导致 sticky 边界计算错误
This commit is contained in:
parent
99d351eb1f
commit
d8f7f2f94f
@ -995,6 +995,10 @@ export default {
|
|||||||
clearTimeout(this._scrollUnlockTimer);
|
clearTimeout(this._scrollUnlockTimer);
|
||||||
this._scrollUnlockTimer = null;
|
this._scrollUnlockTimer = null;
|
||||||
}
|
}
|
||||||
|
if (this._scrollSettleTimer) {
|
||||||
|
clearTimeout(this._scrollSettleTimer);
|
||||||
|
this._scrollSettleTimer = null;
|
||||||
|
}
|
||||||
const scrollRoot = this.getScrollRoot();
|
const scrollRoot = this.getScrollRoot();
|
||||||
if (scrollRoot && this._scrollEndHandler) {
|
if (scrollRoot && this._scrollEndHandler) {
|
||||||
scrollRoot.removeEventListener('scrollend', this._scrollEndHandler);
|
scrollRoot.removeEventListener('scrollend', this._scrollEndHandler);
|
||||||
@ -1012,7 +1016,7 @@ export default {
|
|||||||
this.suppressSectionObserver = true;
|
this.suppressSectionObserver = true;
|
||||||
this.activeTabIndex = tabIndex;
|
this.activeTabIndex = tabIndex;
|
||||||
|
|
||||||
// 瞬时定位路径:不依赖 900ms 定时器/scrollend 事件去延后解锁,
|
// 瞬时定位路径:不依赖定时器/scrollend 事件去延后解锁,
|
||||||
// 由调用方(典型是 navigateToRouteAnchor)直接控制解锁时机。
|
// 由调用方(典型是 navigateToRouteAnchor)直接控制解锁时机。
|
||||||
if (options.skipObserverLock) return;
|
if (options.skipObserverLock) return;
|
||||||
|
|
||||||
@ -1028,7 +1032,30 @@ export default {
|
|||||||
if (scrollRoot) {
|
if (scrollRoot) {
|
||||||
this._scrollEndHandler = done;
|
this._scrollEndHandler = done;
|
||||||
scrollRoot.addEventListener('scrollend', done, { once: true });
|
scrollRoot.addEventListener('scrollend', done, { once: true });
|
||||||
|
|
||||||
|
// scrollend 兼容性轮询兜底:不支持该事件的浏览器(如部分 Firefox/Safari)
|
||||||
|
// 改为轮询 scrollTop 稳定后解锁,防止 900ms 定时器过早或过晚触发
|
||||||
|
let settleChecks = 0;
|
||||||
|
let previousTop = scrollRoot.scrollTop;
|
||||||
|
const pollUntilSettled = () => {
|
||||||
|
if (unlocked) return;
|
||||||
|
const nextTop = scrollRoot.scrollTop;
|
||||||
|
if (Math.abs(nextTop - previousTop) <= 1) {
|
||||||
|
settleChecks += 1;
|
||||||
|
if (settleChecks >= 2) {
|
||||||
|
done();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
settleChecks = 0;
|
||||||
|
previousTop = nextTop;
|
||||||
|
}
|
||||||
|
this._scrollSettleTimer = setTimeout(pollUntilSettled, 80);
|
||||||
|
};
|
||||||
|
this._scrollSettleTimer = setTimeout(pollUntilSettled, 80);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 保守超时兜底:即使 scrollend + 轮询都没触发,900ms 后也强制解锁
|
||||||
this._scrollUnlockTimer = setTimeout(done, 900);
|
this._scrollUnlockTimer = setTimeout(done, 900);
|
||||||
},
|
},
|
||||||
onSideNavClick(sectionId, tabIndex) {
|
onSideNavClick(sectionId, tabIndex) {
|
||||||
@ -1340,6 +1367,7 @@ export default {
|
|||||||
_measureStickyGeometry() {
|
_measureStickyGeometry() {
|
||||||
const sidebarEl = this.$el?.querySelector('.gxnlpt-sidebar');
|
const sidebarEl = this.$el?.querySelector('.gxnlpt-sidebar');
|
||||||
const stickyEl = document.body.querySelector('.gxnlpt-sidebar-sticky');
|
const stickyEl = document.body.querySelector('.gxnlpt-sidebar-sticky');
|
||||||
|
const scrollRoot = this._sidebarStickyScrollEl;
|
||||||
if (!sidebarEl || !stickyEl) return null;
|
if (!sidebarEl || !stickyEl) return null;
|
||||||
|
|
||||||
const sidebarRect = sidebarEl.getBoundingClientRect();
|
const sidebarRect = sidebarEl.getBoundingClientRect();
|
||||||
@ -1361,8 +1389,12 @@ export default {
|
|||||||
// sticky 视觉 left/width 跟着 sidebar 容器的视觉 left/width 走
|
// sticky 视觉 left/width 跟着 sidebar 容器的视觉 left/width 走
|
||||||
sidebarVisualLeft: sidebarRect.left,
|
sidebarVisualLeft: sidebarRect.left,
|
||||||
sidebarVisualWidth: sidebarRect.width,
|
sidebarVisualWidth: sidebarRect.width,
|
||||||
// sidebar 容器初始视觉 top(scrollTop=0 时)。后续 -scrollTop 推算
|
// 关键:sidebarInitialTop 必须是"scrollTop = 0 时 sidebar 顶部到视口顶部的距离"。
|
||||||
sidebarInitialTop: sidebarRect.top,
|
// sidebarRect.top 是当前 scrollTop 下的测量值,两者相加才得到 scroll 0 时的位置。
|
||||||
|
// 如果不加 scrollTop 修正,在 keep-alive 缓存页面返回、或页面已滚动时初始化,
|
||||||
|
// sidebarInitialTop 会是错误值,导致 _applySidebarSticky 中 sidebarTopVisual 计算错误,
|
||||||
|
// 最终 sticky 元素被钳到 navOffset 附近,出现顶部空白或跳变。
|
||||||
|
sidebarInitialTop: sidebarRect.top + (scrollRoot ? scrollRoot.scrollTop : 0),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
initSidebarSticky() {
|
initSidebarSticky() {
|
||||||
@ -1451,6 +1483,7 @@ export default {
|
|||||||
this._lastStickyTop = undefined;
|
this._lastStickyTop = undefined;
|
||||||
this._lastStickyLeft = undefined;
|
this._lastStickyLeft = undefined;
|
||||||
this._lastStickyWidth = undefined;
|
this._lastStickyWidth = undefined;
|
||||||
|
this._lastStickyVisible = undefined;
|
||||||
|
|
||||||
// sticky 已经被 portal 到 body 下,所以从 body 找
|
// sticky 已经被 portal 到 body 下,所以从 body 找
|
||||||
const stickyEl = document.body.querySelector('.gxnlpt-sidebar-sticky');
|
const stickyEl = document.body.querySelector('.gxnlpt-sidebar-sticky');
|
||||||
@ -1546,7 +1579,7 @@ export default {
|
|||||||
this._lastStickyLeft = g.sidebarVisualLeft;
|
this._lastStickyLeft = g.sidebarVisualLeft;
|
||||||
this._lastStickyWidth = g.sidebarVisualWidth;
|
this._lastStickyWidth = g.sidebarVisualWidth;
|
||||||
|
|
||||||
// 设置 fixed 元素的 left/width/top
|
// 设置 fixed 元素的 left/width/top,sticky 始终可见(固定在左侧)
|
||||||
stickyEl.style.left = `${g.sidebarVisualLeft}px`;
|
stickyEl.style.left = `${g.sidebarVisualLeft}px`;
|
||||||
stickyEl.style.width = `${g.sidebarVisualWidth}px`;
|
stickyEl.style.width = `${g.sidebarVisualWidth}px`;
|
||||||
stickyEl.style.top = `${stickyTopVisual}px`;
|
stickyEl.style.top = `${stickyTopVisual}px`;
|
||||||
@ -1686,7 +1719,11 @@ html.portal-figma-scale-active .gxnlpt-page {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
width: @gxnlpt-sidebar-width;
|
width: @gxnlpt-sidebar-width;
|
||||||
min-height: 0;
|
/* 关键:sticky 被 portal 到 body 后,sidebar 只剩 bg+tail 撑高度。
|
||||||
|
若 min-height: 0,容器会塌缩到几乎为 0,导致 _measureStickyGeometry()
|
||||||
|
测到的 sidebarHeight 很小,使 stickyMaxTopVisual 变成负值,
|
||||||
|
top 被钳到视口外消失。设为此值确保 sidebar 始终至少和内容区同高。*/
|
||||||
|
min-height: @gxnlpt-content-min-height;
|
||||||
padding: @gxnlpt-sidebar-padding;
|
padding: @gxnlpt-sidebar-padding;
|
||||||
border-radius: @gxnlpt-sidebar-radius;
|
border-radius: @gxnlpt-sidebar-radius;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user