Vue监听滚动实现锚点定位(双向)

这里很感谢 http://www.jb51.net/article/110325.htm 这篇文章带来的启发

但是我和他不同,网上的方法都是  这样计算滚动条距离窗口顶部的距离,注意是窗口,用的是document对象

 

 // Chrome
 document.body.scrollTop
 // Firefox
 document.documentElement.scrollTop
 // Safari
 window.pageYOffset

我这项目就无法正常这样使用了,首先我们vue项目有个总页面layout组件,左侧分 menu组件,最上方fixed了两块div,最后加上content页面内容给用户看的主界面,所有的页面都是这样渲染进layout__content里的 这就导致无法使用document对象来获取滚动条高度

我调试发现,页面在某一块 上有css,滚动条就在这上面,所以,我这里使用的@scroll方法,

注意是@scroll 而不是scroll.native,

  handleScroll (el) {
        this.scrollTop = this.$refs.content.scrollTop
      },

 

### Vue3 中实现点定位双向滚动效果 在 Vue3 或 Nuxt3 项目中,可以通过监听滚动事件以及结合 `IntersectionObserver` API 来实现点定位的 **双向滚动效果**。这种效果通常用于文档类网站或博客,当用户击某个菜单项时会平滑滚动到对应的章节;而当用户手动滚动页面时,也会自动更新菜单中的激活状态。 以下是具体实现方法: #### 1. 页面结构设计 HTML 结构分为两部分:左侧为导航菜单,右侧为内容区域。每个导航菜单项对应一个内容区块。 ```html <template> <div class="container"> <!-- 左侧导航 --> <nav id="menu" class="sidebar"> <ul> <li v-for="(item, index) in sections" :key="index" @click="scrollToSection(item.id)"> {{ item.name }} </li> </ul> </nav> <!-- 右侧内容 --> <main class="content"> <section v-for="(section, idx) in sections" :id="section.id" :key="'sec-' + idx"> <h2>{{ section.name }}</h2> <p>这里是{{ section.name}}的内容...</p> </section> </main> </div> </template> ``` --- #### 2. 数据定义 通过数据绑定的方式管理导航菜单和内容区块的关系。 ```javascript <script setup> import { ref, onMounted } from 'vue'; const sections = [ { name: '简介', id: 'introduction' }, { name: '特', id: 'features' }, { name: '安装', id: 'installation' }, { name: '使用指南', id: 'usage-guide' } ]; // 当前活动的菜单索引 const activeIndex = ref(0); onMounted(() => { observeSections(); }); </script> ``` --- #### 3. 平滑滚动逻辑 利用 JavaScript 的 `scrollIntoView` 方法实现击导航菜单后的平滑滚动效果。 ```javascript function scrollToSection(id) { const element = document.getElementById(id); if (element) { element.scrollIntoView({ behavior: 'smooth', block: 'start' }); } } ``` --- #### 4. 自动高亮当前菜单项 为了实现在用户滚动页面时动态更新导航菜单的状态,可以借助 `IntersectionObserver` API 检测各个区块是否进入视口范围。 ```javascript function observeSections() { const options = { rootMargin: '-50% 0px -50% 0px', threshold: 0.5, }; const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { // 更新当前活跃的菜单项 updateActiveMenu(entry.target.getAttribute('id')); } }); }, options); // 遍历所有 content 块并观察它们 sections.forEach(({ id }) => { const el = document.getElementById(id); if (el) observer.observe(el); }); } function updateActiveMenu(activeId) { activeIndex.value = sections.findIndex((s) => s.id === activeId); } ``` --- #### 5. 样式调整 为了让用户体验更佳,可设置样式来突出显示当前激活的菜单项。 ```css <style scoped> .sidebar ul li { cursor: pointer; padding: 8px 16px; transition: background-color 0.3s ease-in-out; } .sidebar ul li:hover { background-color: #f0f0f0; } /* 设置激活状态 */ .sidebar ul li.active { font-weight: bold; color: blue; background-color: lightblue; } .content h2 { margin-top: 50px; /* 确保有足够空间触发交集检测 */ } </style> ``` --- #### 完整代码示例 将上述片段组合起来即可完成整个功能模块。 ```html <template> <div class="container"> <nav id="menu" class="sidebar"> <ul> <li v-for="(item, index) in sections" :key="index" :class="{ active: activeIndex === index }" @click="scrollToSection(item.id)" > {{ item.name }} </li> </ul> </nav> <main class="content"> <section v-for="(section, idx) in sections" :id="section.id" :key="'sec-' + idx"> <h2>{{ section.name }}</h2> <p>这里是{{ section.name }}的内容...</p> </section> </main> </div> </template> <script setup> import { ref, onMounted } from 'vue'; const sections = [ { name: '简介', id: 'introduction' }, { name: '特', id: 'features' }, { name: '安装', id: 'installation' }, { name: '使用指南', id: 'usage-guide' }, ]; const activeIndex = ref(0); function scrollToSection(id) { const element = document.getElementById(id); if (element) { element.scrollIntoView({ behavior: 'smooth', block: 'start' }); } } function observeSections() { const options = { rootMargin: '-50% 0px -50% 0px', threshold: 0.5, }; const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { updateActiveMenu(entry.target.getAttribute('id')); } }); }, options); sections.forEach(({ id }) => { const el = document.getElementById(id); if (el) observer.observe(el); }); } function updateActiveMenu(activeId) { activeIndex.value = sections.findIndex((s) => s.id === activeId); } onMounted(() => { observeSections(); }); </script> <style scoped> .sidebar ul li { cursor: pointer; padding: 8px 16px; transition: background-color 0.3s ease-in-out; } .sidebar ul li:hover { background-color: #f0f0f0; } .sidebar ul li.active { font-weight: bold; color: blue; background-color: lightblue; } .content h2 { margin-top: 50px; } </style> ``` ---
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值