fix(ui): 滑动切换只淡出页框内元素(页签行不动)+ 页签行跟随选中项
用户反馈两点,均已实测定位: ① 整框(含顶部页签行)跟着淡出淡入 —— 不该动页签。 原实现把 opacity 打在 .item-group-container 上(它同时包含页签行与内容)。 改为只对 .minimal-section-inner.selected > *(.section-items + .minimal-widget-wrap) 做 keyframes 淡出/淡入;框体与页签行保持不动。 用 keyframes 而非 transition:这些子元素自带 `transition: all`,改 transition 会 覆盖它们原有的 hover 过渡。 ② 页签行不联动 —— 实测 .minimal-headings-row 可横向滚动 (scrollWidth 1306 > clientWidth 828)但 scrollLeft 恒为 0, 切到第 7/8 个分组时选中页签落在视口外(x=1374..1532 > 1280), 用户看不出切换发生。新增 swipeRevealTab() 把选中项滚进可视区(居中,reduced-motion 下瞬时), 手势切换与手动点页签都会触发。 另:边界回弹提示也改为只动页框内容,不再带动页签行。
This commit is contained in:
18
web/navi.css
18
web/navi.css
@ -2490,3 +2490,21 @@ html[data-theme] .minimal-home.navi-dragging * {
|
||||
user-select: none !important;
|
||||
-webkit-user-select: none !important;
|
||||
}
|
||||
|
||||
/* ═══ v91:滑动切换的淡出淡入只作用于"页框内的元素" ═══
|
||||
用户反馈:① 此前整个内容框(含顶部页签行)一起淡出淡入 —— 页签不该闪;
|
||||
② 页签行不联动(实测 .minimal-headings-row scrollWidth 1306 > clientWidth 828,
|
||||
可横向滚动但 scrollLeft 恒为 0;切到第 7/8 个分组时选中页签在视口外)。
|
||||
做法:淡出/淡入只打 .minimal-section-inner.selected 的直接子元素
|
||||
(= .section-items 卡片 + .minimal-widget-wrap),框体与页签行不动。
|
||||
用 keyframes 而不是 transition:这些子元素自带 `transition: all`,
|
||||
改 transition 会覆盖它们原有的 hover 过渡。
|
||||
页签行跟随由 navi.js 的 swipeRevealTab() 负责(滚动居中,reduced-motion 下瞬时)。 */
|
||||
@keyframes navi-section-out { to { opacity: .12; } }
|
||||
@keyframes navi-section-in { from { opacity: .12; } to { opacity: 1; } }
|
||||
html[data-theme] .minimal-home.navi-switching .minimal-section-inner.selected > * {
|
||||
animation: navi-section-out 90ms ease forwards !important;
|
||||
}
|
||||
html[data-theme] .minimal-home.navi-fadein .minimal-section-inner.selected > * {
|
||||
animation: navi-section-in 150ms ease !important;
|
||||
}
|
||||
|
||||
63
web/navi.js
63
web/navi.js
@ -655,23 +655,39 @@
|
||||
}
|
||||
// 到头时的一次性提示:轻微回弹(transform / 仅 8px),禁用动效时退化为淡闪
|
||||
function swipeEdgeHint(dir) {
|
||||
const frame = swipeFrameEl();
|
||||
if (!frame || frame.dataset.naviHint === '1') return;
|
||||
frame.dataset.naviHint = '1';
|
||||
setTimeout(() => { delete frame.dataset.naviHint; }, 300);
|
||||
// 只动"页框内容"(.minimal-section-inner),不动页签行与框体本身
|
||||
const el = document.querySelector('.minimal-section-inner.selected');
|
||||
if (!el || el.dataset.naviHint === '1') return;
|
||||
el.dataset.naviHint = '1';
|
||||
setTimeout(() => { delete el.dataset.naviHint; }, 320);
|
||||
if (reducedMotion()) {
|
||||
frame.style.transition = 'opacity 110ms ease';
|
||||
frame.style.opacity = '0.75';
|
||||
setTimeout(() => { frame.style.opacity = ''; frame.style.transition = ''; }, 120);
|
||||
el.style.transition = 'opacity 110ms ease';
|
||||
el.style.opacity = '0.78';
|
||||
setTimeout(() => { el.style.opacity = ''; el.style.transition = ''; }, 120);
|
||||
return;
|
||||
}
|
||||
frame.style.transition = 'transform 150ms cubic-bezier(.22,1,.36,1)';
|
||||
frame.style.transform = 'translateX(' + (dir > 0 ? -8 : 8) + 'px)';
|
||||
el.style.transition = 'transform 150ms cubic-bezier(.22,1,.36,1)';
|
||||
el.style.transform = 'translateX(' + (dir > 0 ? -8 : 8) + 'px)';
|
||||
setTimeout(() => {
|
||||
frame.style.transform = '';
|
||||
setTimeout(() => { frame.style.transition = ''; }, 170);
|
||||
el.style.transform = '';
|
||||
setTimeout(() => { el.style.transition = ''; }, 170);
|
||||
}, 150);
|
||||
}
|
||||
// 页签行跟随:实测 .minimal-headings-row 可横向滚动(scrollWidth 1306 > clientWidth 828),
|
||||
// 但 scrollLeft 恒为 0 —— 切到第 7/8 个分组时选中页签落在视口外,
|
||||
// 用户会以为"手势没生效 / 页签没联动"。这里把选中项滚进可视区(居中)。
|
||||
function swipeRevealTab(tab) {
|
||||
const row = tab && tab.closest ? tab.closest('.minimal-headings-row') : null;
|
||||
if (!row) return;
|
||||
const tr = tab.getBoundingClientRect(), rr = row.getBoundingClientRect();
|
||||
if (tr.left >= rr.left - 1 && tr.right <= rr.right + 1) return; // 已完整可见
|
||||
const delta = (tr.left + tr.width / 2) - (rr.left + rr.width / 2);
|
||||
const max = Math.max(0, row.scrollWidth - row.clientWidth);
|
||||
const left = Math.max(0, Math.min(max, row.scrollLeft + delta));
|
||||
if (reducedMotion() || !row.scrollTo) row.scrollLeft = left;
|
||||
else row.scrollTo({ left: left, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
async function swipeSwitch(dir) {
|
||||
if (swipeBusy || Date.now() < swipeCoolUntil) return false;
|
||||
const tabs = swipeTabs();
|
||||
@ -681,13 +697,11 @@
|
||||
if (j < 0 || j >= tabs.length) { swipeEdgeHint(dir); return false; } // clamp
|
||||
swipeBusy = true;
|
||||
swipeCoolUntil = Date.now() + SWIPE.cooldown;
|
||||
const frame = swipeFrameEl();
|
||||
const fade = frame && !reducedMotion();
|
||||
if (fade) {
|
||||
frame.style.transition = 'opacity 90ms ease';
|
||||
frame.style.opacity = '0.28';
|
||||
await later(90);
|
||||
}
|
||||
// 淡出/淡入只作用于"页框内的元素"(.minimal-section-inner.selected > *),
|
||||
// 页签行与框体本身保持不动(用户反馈:此前整框一起闪,页签也被带着淡出了)。
|
||||
const home = document.querySelector('.minimal-home');
|
||||
const fade = !!home && !reducedMotion();
|
||||
if (fade) { home.classList.add('navi-switching'); await later(90); }
|
||||
// 关键:这是我们自己发起的切换 click —— 必须绕开"防误触"拦截,
|
||||
// 否则会出现"手势收到了、手势条件也满足,但页面没切"的怪象(实测踩过)。
|
||||
swipeEatClick = 0;
|
||||
@ -695,10 +709,11 @@
|
||||
try { tabs[j].click(); } finally { swipeSelfClick = false; }
|
||||
await later(70);
|
||||
if (fade) {
|
||||
frame.style.transition = 'opacity 150ms ease';
|
||||
frame.style.opacity = '1';
|
||||
setTimeout(() => { frame.style.transition = ''; frame.style.opacity = ''; }, 190);
|
||||
home.classList.remove('navi-switching');
|
||||
home.classList.add('navi-fadein');
|
||||
setTimeout(() => home.classList.remove('navi-fadein'), 220);
|
||||
}
|
||||
swipeRevealTab(tabs[j]); // 页签行跟随选中项
|
||||
swipeBusy = false;
|
||||
return true;
|
||||
}
|
||||
@ -764,6 +779,12 @@
|
||||
}
|
||||
}, { passive: true });
|
||||
|
||||
// 用户自己点页签时,页签行同样跟随(把选中项滚进可视区)
|
||||
frame.addEventListener('click', (evt) => {
|
||||
const tab = evt.target && evt.target.closest ? evt.target.closest('.minimal-section-heading.headings') : null;
|
||||
if (tab) setTimeout(() => swipeRevealTab(tab), 80);
|
||||
}, { passive: true });
|
||||
|
||||
if (!swipeDocBound) {
|
||||
swipeDocBound = true;
|
||||
// 滑动后吞掉紧随的一次 click,避免误开卡片里的链接
|
||||
|
||||
Reference in New Issue
Block a user