左侧边栏标题列表实现:
-------------------html-----------------------
<div
class="uav"
:class="{ hidden: !isVisible, visible: isVisible }">
<ul id="toc">
<li
v-for="(item, index) in HotList"
:key="index"
:class="{ active: activeSectionId == item.id }"
class="point tocbox">
<a @click.prevent="scrollToSection(item.id)"> {{ item.name }}</a>
</li>
</ul>
</div>
--------------------js(v3)--------------------
//标题列表数据获取
const HotList = ref([]);
const ObtainHotBox = () => {
GetHotBox().then((res) => {
if (res.data && res.data.data) {
// 过滤出 box 不为空的数据
const filteredData = res.data.data.filter(item => item.box && item.box.length > 0);
HotList.value = filteredData;
}
});
};
//实现点击左侧标题 中间内容区域滚动到对应标题位置
const isManualScrolling = ref(false)
const scrollToSection = (sectionId) => {
// 启用手动滚动标记
isManualScrolling.value = true
const element = document.getElementById(sectionId);
if (!element) return;
console.log(`滚动到 id 为 ${sectionId} 的元素`, element);
if (element) {
const elementTop = element.getBoundingClientRect().top + window.scrollY;
window.scrollTo({
top: elementTop - 89, //减去固定头部高度
behavior: "smooth",
});
} else {
console.log(`未找到 id 为 ${sectionId} 的元素`);
}
// 更新选中的区块ID
activeSectionId.value = sectionId;
// 500ms后释放锁定(超过滚动动画时间)
setTimeout(() => {
isManualScrolling.value = false
}, 800)
};
//检测中间区域内容
const isVisible = ref(false);
const targetPosition = ref(400);
const activeSectionId = ref(null);
const updateActiveSection = () => {
// 如果是手动触发的滚动,跳过自动检测
if (isManualScrolling.value) return
// 监听滚动事件
// 使用 window.scrollY 替代已弃用的 window.pageYOffset
if (window.scrollY > targetPosition.value) {
isVisible.value = true;
} else {
isVisible.value = false;
}
const sections = document.querySelectorAll(".Title");
let closest = null; // 初始值设为上一次的值
let minDistance = Infinity;
console.log('sections')
sections.forEach((section) => {
const rect = section.getBoundingClientRect();
// 检查区块是否在视窗内或上方(但不超过视窗高度)
if (rect.top >= 0 && rect.top < window.innerHeight) {
// 选择最接近视窗顶部的区块
if (rect.top <= minDistance) {
minDistance = rect.top;
closest = section.id;
}
}
});
activeSectionId.value = closest;
// console.log('当前激活的区块 ID:', activeSectionId.value,closest);
};
onMounted(async () => {
window.addEventListener("scroll", updateActiveSection);
});
onUnmounted(() => {
window.removeEventListener("scroll", updateActiveSection);
});
--------------------css------------------
.uav {
position: fixed;
left: 0.4rem;
top: 30%;
z-index: 99;
#toc {
width: 1.82rem;
font-weight: 400;
text-align: center;
font-size: 0.12rem;
color: #9373c3;
background: url("../../common/assets/images/maodianbg.png") no-repeat;
background-size: 100% 100%;
padding: 0.2rem 0;
.tocbox {
width: 1.36rem;
text-align: center;
margin: 0.22rem auto;
}
.active {
/* 选中状态的样式 */
color: #c7c7ff;
background: url("../../common/assets/images/acmaodian.png") no-repeat;
background-size: 100% 100%;
transition: all 0.3s ease-out;
will-change: transform, opacity;
}
/* 隐藏默认滚动条减少视觉干扰 */
html {
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE */
}
::-webkit-scrollbar {
display: none; /* Chrome/Safari */
}
}
}
中间区域内容联动:给列表项添加Title类名,以及绑定动态:id="item.id"
<div class="mb-[1.6rem]">
<div
v-show="item.box.length"
v-for="(item, index) in homeBoxList"
:key="item.id">
<div
v-if="item.src"
class="flex-c mb-[0.1rem] Title"
:id="item.id"
>
<div class="w-[5.5rem] h-[0.69rem] flex-c">
<img class="h-full" :src="item.src" alt="" srcset="" />
</div>
</div>
...