Browse Source

style(about-history): 调整导航交互

master
peijunlei 2 weeks ago
parent
commit
d5ebbe2c15
  1. 24
      public/themes/dist_static/static/css/about-history.css
  2. 120
      public/themes/dist_static/static/js/about-history.js

24
public/themes/dist_static/static/css/about-history.css

@ -57,7 +57,9 @@
font-weight: 400; font-weight: 400;
line-height: var(--font-24); line-height: var(--font-24);
text-decoration: none; text-decoration: none;
transition: color 0.25s ease, font-size 0.25s ease, font-weight 0.25s ease;
transform: scale(1);
transform-origin: right center;
transition: color 0.25s ease, transform 0.25s ease, font-weight 0.25s ease;
} }
.ah-nav__item:hover { .ah-nav__item:hover {
@ -65,10 +67,12 @@
} }
.ah-nav__item.is-active { .ah-nav__item.is-active {
z-index: 1;
color: var(--color-text); color: var(--color-text);
font-size: var(--font-64);
font-weight: 500; font-weight: 500;
line-height: 1;
/* 约等于原 font-64 / font-24,不占布局,避免进度条回弹 */
transform: scale(calc(64 / 24));
margin: 1rem 0;
} }
.ah-nav__rail { .ah-nav__rail {
@ -94,18 +98,18 @@
background: var(--color-primary); background: var(--color-primary);
} }
/* 红线:从原点向下变长,不位移 */
/* 红线:铺满轨道高度,用 scaleY 表示进度(合成层,不触发布局) */
.ah-nav__indicator { .ah-nav__indicator {
position: absolute; position: absolute;
left: 50%;
top: 0; top: 0;
left: 0;
width: 2px; width: 2px;
height: 0;
margin-left: -1px;
height: 100%;
background: var(--color-primary); background: var(--color-primary);
border-radius: 1px; border-radius: 1px;
transform: none;
transition: height 0.3s ease;
transform: scaleY(0.04);
transform-origin: top center;
will-change: transform;
} }
.ah-content { .ah-content {
@ -255,9 +259,7 @@
.ah-nav__item.is-active { .ah-nav__item.is-active {
color: var(--color-text); color: var(--color-text);
font-size: var(--font-18);
font-weight: 500; font-weight: 500;
line-height: 1;
transform: scale(1.45); transform: scale(1.45);
transform-origin: center; transform-origin: center;
} }

120
public/themes/dist_static/static/js/about-history.js

@ -21,6 +21,10 @@ function getHeaderOffset() {
return h + 24; return h + 24;
} }
function clamp(n, min, max) {
return Math.min(max, Math.max(min, n));
}
function initHistorySpy() { function initHistorySpy() {
const root = document.querySelector("[data-ah-spy]"); const root = document.querySelector("[data-ah-spy]");
if (!root) return; if (!root) return;
@ -33,43 +37,62 @@ function initHistorySpy() {
let activeYear = navItems[0].dataset.year; let activeYear = navItems[0].dataset.year;
let clicking = false; let clicking = false;
let rafId = 0;
const isMobile = () => window.matchMedia("(max-width: 992px)").matches;
const syncRailHeight = () => { const syncRailHeight = () => {
if (!yearsWrap || !rail) return;
if (!yearsWrap || !rail || isMobile()) return;
rail.style.height = `${yearsWrap.offsetHeight}px`; rail.style.height = `${yearsWrap.offsetHeight}px`;
}; };
const updateLine = (item) => {
if (!indicator || !item || !yearsWrap) return;
syncRailHeight();
const itemRect = item.getBoundingClientRect();
const yearsRect = yearsWrap.getBoundingClientRect();
// 原点固定在顶部,线条延伸到当前高亮年份底部
const height = Math.max(itemRect.bottom - yearsRect.top, 8);
indicator.style.height = `${height}px`;
indicator.style.transform = "";
const setProgress = (progress) => {
if (!indicator) return;
indicator.style.transform = `scaleY(${clamp(progress, 0.04, 1)})`;
}; };
const updateLineSoon = (item) => {
updateLine(item);
requestAnimationFrame(() => updateLine(item));
window.setTimeout(() => updateLine(item), 320);
const getActiveIndex = () => {
const offset = getHeaderOffset() + 8;
let idx = 0;
for (let i = 0; i < blocks.length; i++) {
if (blocks[i].getBoundingClientRect().top <= offset) idx = i;
}
return idx;
}; };
const setActive = (year) => {
if (!year || year === activeYear) {
const current = navItems.find((el) => el.dataset.year === activeYear);
updateLineSoon(current);
/**
* 进度只跟内容滚动的年份序号插值不依赖左侧字号布局
* 跨年时 (idx+local)/(n-1) 单调连续避免先变短再变长
*/
const updateProgressFromScroll = () => {
if (!indicator || !rail || isMobile()) return;
syncRailHeight();
const n = navItems.length;
if (n <= 1) {
setProgress(1);
return; return;
} }
const offset = getHeaderOffset() + 8;
const idx = getActiveIndex();
let local = 1;
if (blocks[idx + 1]) {
const curTop = blocks[idx].getBoundingClientRect().top;
const nextTop = blocks[idx + 1].getBoundingClientRect().top;
const span = nextTop - curTop;
local = span > 0 ? clamp((offset - curTop) / span, 0, 1) : 1;
}
setProgress((idx + local) / (n - 1));
};
const setActive = (year, { scrollNav = true } = {}) => {
if (!year || year === activeYear) return;
activeYear = year; activeYear = year;
navItems.forEach((el) => { navItems.forEach((el) => {
el.classList.toggle("is-active", el.dataset.year === year); el.classList.toggle("is-active", el.dataset.year === year);
}); });
if (scrollNav && isMobile()) {
const current = navItems.find((el) => el.dataset.year === year); const current = navItems.find((el) => el.dataset.year === year);
updateLineSoon(current);
if (current && current.closest(".ah-nav__years") && window.matchMedia("(max-width: 992px)").matches) {
current.scrollIntoView({
current?.scrollIntoView({
inline: "center", inline: "center",
block: "nearest", block: "nearest",
behavior: "smooth" behavior: "smooth"
@ -77,6 +100,20 @@ function initHistorySpy() {
} }
}; };
const onScroll = () => {
if (rafId) return;
rafId = requestAnimationFrame(() => {
rafId = 0;
// 点击滚动过程中仍跟手更新进度;仅锁定高亮年份,避免中途抢焦点
if (!clicking) {
const idx = getActiveIndex();
const year = navItems[idx]?.dataset.year;
if (year) setActive(year);
}
updateProgressFromScroll();
});
};
const scrollToYear = (year) => { const scrollToYear = (year) => {
const target = blocks.find((el) => el.dataset.year === year); const target = blocks.find((el) => el.dataset.year === year);
if (!target) return; if (!target) return;
@ -85,6 +122,7 @@ function initHistorySpy() {
const top = window.scrollY + target.getBoundingClientRect().top - getHeaderOffset(); const top = window.scrollY + target.getBoundingClientRect().top - getHeaderOffset();
const done = () => { const done = () => {
clicking = false; clicking = false;
updateProgressFromScroll();
}; };
if (window.lenis) { if (window.lenis) {
window.lenis.scrollTo(top, { window.lenis.scrollTo(top, {
@ -107,39 +145,25 @@ function initHistorySpy() {
}); });
}); });
const io = new IntersectionObserver((entries) => {
if (clicking) return;
const visible = entries.filter((e) => e.isIntersecting).sort((a, b) => b.intersectionRatio - a.intersectionRatio);
if (!visible.length) return;
const year = visible[0].target.getAttribute("data-year");
if (year) setActive(year);
}, {
root: null,
rootMargin: `-${getHeaderOffset()}px 0px -45% 0px`,
threshold: [0.05, 0.2, 0.4, 0.6]
});
blocks.forEach((block) => io.observe(block));
if (window.lenis) window.lenis.on("scroll", onScroll);
else window.addEventListener("scroll", onScroll, { passive: true });
const onScrollFallback = () => {
if (clicking) return;
const offset = getHeaderOffset() + 8;
let current = blocks[0]?.dataset.year;
for (const block of blocks) {
if (block.getBoundingClientRect().top <= offset) current = block.dataset.year;
if (typeof ResizeObserver !== "undefined" && yearsWrap) {
new ResizeObserver(() => {
syncRailHeight();
updateProgressFromScroll();
}).observe(yearsWrap);
} }
if (current) setActive(current);
};
if (window.lenis) window.lenis.on("scroll", onScrollFallback);
else window.addEventListener("scroll", onScrollFallback, { passive: true });
setActive(activeYear);
window.addEventListener("resize", () => { window.addEventListener("resize", () => {
const current = navItems.find((el) => el.dataset.year === activeYear);
updateLineSoon(current);
syncRailHeight();
updateProgressFromScroll();
}); });
navItems[0]?.classList.add("is-active");
syncRailHeight();
updateProgressFromScroll();
const hashYear = (location.hash || "").replace(/^#year-/, ""); const hashYear = (location.hash || "").replace(/^#year-/, "");
if (hashYear && blocks.some((el) => el.dataset.year === hashYear)) { if (hashYear && blocks.some((el) => el.dataset.year === hashYear)) {
requestAnimationFrame(() => scrollToYear(hashYear)); requestAnimationFrame(() => scrollToYear(hashYear));

Loading…
Cancel
Save