修改样式

This commit is contained in:
jack
2026-07-10 15:13:39 +08:00
parent 3eadf7526e
commit 251ab53040
2 changed files with 91 additions and 48 deletions
+4 -4
View File
@@ -203,8 +203,8 @@ html {
}
.box_3 {
background-color: rgba(25, 140, 255, 0.1);
border-radius: 18px;
/* 跟留资/安全一致:Slice_6 本身就是带缺角的"花瓣圆",不再额外垫浅色背景 + 圆角,
否则浅色方块的四个圆角会透出来,被误认为是图标的"四个角" */
height: 1.814rem;
width: 1.814rem;
}
@@ -277,8 +277,8 @@ html {
}
.block_2 {
background-color: rgba(255, 165, 39, 0.1);
border-radius: 18px;
/* 跟留资/安全一致:Slice_7 本身就是带缺角的"花瓣圆",不再额外垫浅色背景 + 圆角,
否则浅色方块的四个圆角会透出来,被误认为是图标的"四个角" */
height: 1.814rem;
width: 1.814rem;
}
+85 -42
View File
@@ -13,11 +13,11 @@
./assets/img/slice-3-black-3x.png 3x
"
/>
<span class="text_1 nav-link" data-spy="top" @click="scrollTo('top')">首页</span>
<span class="text_2 nav-link" data-spy="about" @click="scrollTo('about')">WithMind</span>
<span class="text_3 nav-link" data-spy="skills" @click="scrollTo('skills')">Skills</span>
<span class="text_4 nav-link" data-spy="platform" @click="scrollTo('platform')">创作平台</span>
<span class="text_5 nav-link" data-spy="gateway" @click="scrollTo('gateway')">模型网关</span>
<span class="text_1 nav-link" :class="{ 'nav-active': activeSection === 'top' }" data-spy="top" @click="scrollTo('top')">首页</span>
<span class="text_2 nav-link" :class="{ 'nav-active': activeSection === 'about' }" data-spy="about" @click="scrollTo('about')">WithMind</span>
<span class="text_3 nav-link" :class="{ 'nav-active': activeSection === 'skills' }" data-spy="skills" @click="scrollTo('skills')">Skills</span>
<span class="text_4 nav-link" :class="{ 'nav-active': activeSection === 'platform' }" data-spy="platform" @click="scrollTo('platform')">创作平台</span>
<span class="text_5 nav-link" :class="{ 'nav-active': activeSection === 'gateway' }" data-spy="gateway" @click="scrollTo('gateway')">模型网关</span>
<div class="group_3 flex-col"></div>
</div>
<img
@@ -530,70 +530,113 @@ export default {
this.initScrollSpy();
},
methods: {
/* scroll-spy:用 IntersectionObserver 监听每个 section,
进入视口时把对应 nav 链接加 .nav-active 类(蓝色高亮) */
/* scroll-spy:用 Vue 响应式 data(activeSection) 驱动 nav-link 的 :class 绑定。
关键设计:点击 nav-link 后,锁定 activeSection = id,屏蔽一切自动 update,
直到平滑滚动结束再校准。这样彻底避免"点击瞬间设的 activeSection
被 IO/scroll 回调里的 compute() 改掉"的竞态。 */
initScrollSpy() {
const sections = ['top', 'about', 'skills', 'platform', 'agent', 'media', 'gateway'];
const navLinks = document.querySelectorAll('.section_2 [data-spy]');
const sectionIds = ['about', 'skills', 'platform', 'agent', 'media', 'gateway'];
const observer = new IntersectionObserver(
(entries) => {
/* 多个 entry 同时触发时,选"离顶部最近"的那一个作为 active */
let best = null;
entries.forEach((entry) => {
if (entry.isIntersecting) {
if (!best || entry.boundingClientRect.top < best.boundingClientRect.top) {
best = entry;
}
}
});
if (best) {
const id = best.target.id;
this.activeSection = id;
/* 同步 DOM class(因为 nav-link class 在外部 CSS 不可用,直接 toggle class) */
navLinks.forEach((link) => {
if (link.getAttribute('data-spy') === id) {
link.classList.add('nav-active');
// 单一 compute:section 顶 ≤ 视口高度的 50%(即"section 顶已过视口中线")就算激活
// 这避开 about 段高度只有 ~144px 的边界问题:
// 点 skills 后,skills 顶 ≈ 260px < 375px(50% vh),platform 顶 > 375 → 选 skills
// 而 about 顶 ≈ 116px 也 ≤ 375,但 for 循环从上到下遍历,后一个 skills 覆盖前一个
const compute = () => {
const scrollY = window.scrollY || window.pageYOffset || 0;
if (scrollY < 100) return 'top';
const vh = window.innerHeight || document.documentElement.clientHeight;
const trigger = vh * 0.5;
let activeId = 'top';
for (const id of sectionIds) {
const el = document.getElementById(id);
if (!el) continue;
const top = el.getBoundingClientRect().top;
if (top <= trigger) {
activeId = id;
} else {
link.classList.remove('nav-active');
break;
}
});
}
},
{
/* 视口上 30% / 下 40% 区域作为"激活区",让 section 滚到屏幕上半部时高亮 */
rootMargin: '-30% 0px -40% 0px',
threshold: 0
}
);
return activeId;
};
sections.forEach((id) => {
const update = () => {
// 抑制期(scrollTo 触发的平滑滚动过程中)不响应任何 update
if (Date.now() < (this.scrollSuppressUntil || 0)) return;
this.activeSection = compute();
};
// IO 兜底:section 进入/退出视口时重算
const observer = new IntersectionObserver(() => update(), {
rootMargin: '0px 0px -50% 0px',
threshold: 0
});
sectionIds.forEach((id) => {
const el = document.getElementById(id);
if (el) observer.observe(el);
});
let ticking = false;
window.addEventListener('scroll', () => {
if (ticking) return;
ticking = true;
requestAnimationFrame(() => {
ticking = false;
update();
});
}, { passive: true });
update();
},
scrollTo(id) {
// 1) 立即同步响应式状态(不等滚动动画)
this.activeSection = id;
// 2) 抑制一切自动 update 1.2s(覆盖 smooth 滚动动画)
const suppressMs = 1200;
this.scrollSuppressUntil = Date.now() + suppressMs;
// 3) 抑制期结束后,按当前 scrollY 校准一次
setTimeout(() => {
this.scrollSuppressUntil = 0; // 解除抑制
// 此时页面已稳定,scrollY 是最终值,compute() 必然正确
this.activeSection = (() => {
const sectionIds = ['about', 'skills', 'platform', 'agent', 'media', 'gateway'];
const scrollY = window.scrollY || window.pageYOffset || 0;
if (scrollY < 100) return 'top';
const vh = window.innerHeight || document.documentElement.clientHeight;
const trigger = vh * 0.5;
let activeId = 'top';
for (const sid of sectionIds) {
const el = document.getElementById(sid);
if (!el) continue;
const top = el.getBoundingClientRect().top;
if (top <= trigger) {
activeId = sid;
} else {
break;
}
}
return activeId;
})();
}, suppressMs);
// 4) 触发滚动
if (id === 'top') {
window.scrollTo({ top: 0, behavior: 'smooth' });
return;
}
const el = document.getElementById(id);
if (el) {
// 模型网关特殊处理:滚到网关模块底部,避免露出 footer 黑色背景
if (id === 'gateway') {
// 让网关底部对齐到"视口底部上方 250px"位置,留更大余量,footer 黑色背景完全在视口外
const rect = el.getBoundingClientRect();
const elBottom = rect.bottom + window.pageYOffset;
const target = elBottom - window.innerHeight + 250;
window.scrollTo({ top: Math.max(0, target), behavior: 'smooth' });
return;
}
// 顶部 fixed 导航栏高度 2.134rem(80px),加 180px 余量,共 260px 偏移,完全避开导航遮住
} else {
const top = el.getBoundingClientRect().top + window.pageYOffset - 260;
window.scrollTo({ top: top, behavior: 'smooth' });
}
}
}
}
};
</script>
<style scoped lang="css" src="./assets/index.rem.css" />