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:
liulong 2026-06-08 17:30:07 +08:00
parent 99d351eb1f
commit d8f7f2f94f

View File

@ -995,6 +995,10 @@ export default {
clearTimeout(this._scrollUnlockTimer);
this._scrollUnlockTimer = null;
}
if (this._scrollSettleTimer) {
clearTimeout(this._scrollSettleTimer);
this._scrollSettleTimer = null;
}
const scrollRoot = this.getScrollRoot();
if (scrollRoot && this._scrollEndHandler) {
scrollRoot.removeEventListener('scrollend', this._scrollEndHandler);
@ -1012,7 +1016,7 @@ export default {
this.suppressSectionObserver = true;
this.activeTabIndex = tabIndex;
// : 900ms /scrollend ,
// :/scrollend ,
// ( navigateToRouteAnchor)
if (options.skipObserverLock) return;
@ -1028,7 +1032,30 @@ export default {
if (scrollRoot) {
this._scrollEndHandler = done;
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);
},
onSideNavClick(sectionId, tabIndex) {
@ -1340,6 +1367,7 @@ export default {
_measureStickyGeometry() {
const sidebarEl = this.$el?.querySelector('.gxnlpt-sidebar');
const stickyEl = document.body.querySelector('.gxnlpt-sidebar-sticky');
const scrollRoot = this._sidebarStickyScrollEl;
if (!sidebarEl || !stickyEl) return null;
const sidebarRect = sidebarEl.getBoundingClientRect();
@ -1361,8 +1389,12 @@ export default {
// sticky left/width sidebar left/width
sidebarVisualLeft: sidebarRect.left,
sidebarVisualWidth: sidebarRect.width,
// sidebar top(scrollTop=0 ) -scrollTop
sidebarInitialTop: sidebarRect.top,
// sidebarInitialTop "scrollTop = 0 sidebar "
// sidebarRect.top scrollTop scroll 0
// scrollTop keep-alive
// sidebarInitialTop _applySidebarSticky sidebarTopVisual
// sticky navOffset
sidebarInitialTop: sidebarRect.top + (scrollRoot ? scrollRoot.scrollTop : 0),
};
},
initSidebarSticky() {
@ -1451,6 +1483,7 @@ export default {
this._lastStickyTop = undefined;
this._lastStickyLeft = undefined;
this._lastStickyWidth = undefined;
this._lastStickyVisible = undefined;
// sticky portal body , body
const stickyEl = document.body.querySelector('.gxnlpt-sidebar-sticky');
@ -1546,7 +1579,7 @@ export default {
this._lastStickyLeft = g.sidebarVisualLeft;
this._lastStickyWidth = g.sidebarVisualWidth;
// fixed left/width/top
// fixed left/width/topsticky
stickyEl.style.left = `${g.sidebarVisualLeft}px`;
stickyEl.style.width = `${g.sidebarVisualWidth}px`;
stickyEl.style.top = `${stickyTopVisual}px`;
@ -1686,7 +1719,11 @@ html.portal-figma-scale-active .gxnlpt-page {
flex-direction: column;
align-self: stretch;
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;
border-radius: @gxnlpt-sidebar-radius;
box-sizing: border-box;