vue3 锚点定位 点击滚动高亮

功能描述

点击导航跳到对应模块的起始位置,并且高亮点击的导航;
滚动到相应的模块时,对应的导航也自动高亮;

效果展示

在这里插入图片描述

注意事项

  1. 一定要明确哪个是要滚动的盒子;
  2. 滚动的高度要减去导航栏的高度;
  3. 当前在导航1,点击导航4时,会出现导航2、导航3和导航4依次高亮的现象,定义变量判断当前为点击时不监听滚动事件即可;
    isScroll.value = false

核心代码

onMounted(() => {
  nextTick(() => {
  	// 监听滚动的盒子的滚动事件
    document.getElementById('scrollBox').addEventListener("scroll", onScroll);
  })
})

onBeforeUnmount(() => {
  document.getElementById('scrollBox').removeEventListener('scroll', onScroll)
})


const scrollToAnchor = (id, index) => {
 activeIndex.value = index
  var anchor = document.getElementById(id);
  var scrollBox = document.getElementById('scrollBox')
  nextTick(() => {
    scrollBox.scrollTo({
      top: anchor.offsetTop - 30,
      behavior: 'smooth'
    });
 })
  isScroll.value = false
   // 点击时暂停页面的滚动监听事件,防止点击时导航高亮出现走马灯效果;
  // 此处的定时器存在缺陷,点击完2秒之内滚动还是会偶现走马灯现象;
  let timeId = '';
  clearTimeout(timeId);
  timeId = setTimeout(() => {
    isScroll.value = true;
  }, 2000);
};


// 滚动监听器
const onScroll = ()=> {
  if(!isScroll.value) return
  // 获取所有锚点元素
  const navContents = document.querySelectorAll('.container .section')
  // 所有锚点元素的 offsetTop
  const offsetTopArr = []
  navContents.forEach(item => {
    offsetTopArr.push(item.offsetTop-30)
  })
  // 获取当前文档流的 scrollTop
  const scrollTop = document.getElementById('scrollBox').scrollTop
  // 定义当前点亮的导航下标
  let navIndex = 1
  for (let n = 1; n <= offsetTopArr.length; n++) {
    // 如果 scrollTop 大于等于第 n 个元素的 offsetTop 则说明 n-1 的内容已经完全不可见
    // 那么此时导航索引就应该是 n 了
    if (scrollTop >= offsetTopArr[n-1]) {
      navIndex = n
    }
  }
  // 把下标赋值给 vue 的 data
  activeIndex.value = navIndex
}

完整代码

<template>
  <div class="box" id="scrollBox" v-loading="loading">
    <div class="fixed-box">
      <div v-for="(i) in defaultTabs" :class="{active_anchor: activeIndex === i}">
          <a @click="scrollToAnchor(`section${i}`, i)">
            <span v-if="i == 1">导航1</span>
            <span v-if="i == 2">导航2</span>
            <span v-if="i == 3">导航3</span>
            <span v-if="i == 4">导航4</span>
            <span v-if="i == 5">导航5</span>
          </a>
        </div>
    </div>
    <div class="container" id="printcontent">
      <div class="section" id="section1">
        <h3>日历</h3>
        <el-calendar v-model="value" />
      </div>
      <div class="section" id="section2">
        <h3>带边框列表</h3>
        <el-descriptions class="margin-top" title="" :column="4" border>
          <template v-for="i in 4">
            <el-descriptions-item label="Username">kooriookami</el-descriptions-item>
            <el-descriptions-item label="Telephone">18100000000</el-descriptions-item>
            <el-descriptions-item label="Place" :span="2">Suzhou</el-descriptions-item>
            <el-descriptions-item label="Remarks">123123</el-descriptions-item>
            <el-descriptions-item label="Username">一一一</el-descriptions-item>
            <el-descriptions-item label="Remarks">123123</el-descriptions-item>
            <el-descriptions-item label="Remarks">-</el-descriptions-item>
            <el-descriptions-item label="Remarks">-</el-descriptions-item>
            <el-descriptions-item label="Remarks">-</el-descriptions-item>
          </template>
        </el-descriptions>
      </div>
      <div class="section" id="section3">
        <h3>普通表格</h3>
        <el-table :data="tableData" style="width: 100%">
          <el-table-column prop="date" label="Date" width="180" />
          <el-table-column prop="name" label="Name" width="180" />
          <el-table-column prop="address" label="Address" />
        </el-table>
      </div>
      <div class="section" id="section4">
        <h3>普通卡片</h3>
        <el-card class="box-card">
          <template #header>
            <div class="card-header">
              <span>Card name</span>
              <el-button class="button" text>Operation button</el-button>
            </div>
          </template>
          <div v-for="o in 10" :key="o" class="text item">{{ 'List item ' + o }}</div>
          <template #footer>Footer content</template>
        </el-card>
      </div>
    </div>
  </div>
</template>

<script setup>
import { onMounted, ref, nextTick, onBeforeUnmount } from 'vue'
const loading = ref(false)
const value = ref(new Date())
const defaultTabs = ref([1, 2, 3, 4, 5])
const activeIndex = ref(1)
const isScroll = ref(true) // 点击导航栏时,暂时停止监听页面滚动
const tableData = [
  {
    date: '2016-05-03',
    name: 'Tom',
    address: 'No. 189, Grove St, Los Angeles',
  },
  {
    date: '2016-05-02',
    name: 'Tom',
    address: 'No. 189, Grove St, Los Angeles',
  },
  {
    date: '2016-05-04',
    name: 'Tom',
    address: 'No. 189, Grove St, Los Angeles',
  },
  {
    date: '2016-05-01',
    name: 'Tom',
    address: 'No. 189, Grove St, Los Angeles',
  },
  {
    date: '2016-05-04',
    name: 'Tom',
    address: 'No. 189, Grove St, Los Angeles',
  },
  {
    date: '2016-05-01',
    name: 'Tom',
    address: 'No. 189, Grove St, Los Angeles',
  },
  {
    date: '2016-05-04',
    name: 'Tom',
    address: 'No. 189, Grove St, Los Angeles',
  },
  {
    date: '2016-05-01',
    name: 'Tom',
    address: 'No. 189, Grove St, Los Angeles',
  },
  {
    date: '2016-05-04',
    name: 'Tom',
    address: 'No. 189, Grove St, Los Angeles',
  },
  {
    date: '2016-05-01',
    name: 'Tom',
    address: 'No. 189, Grove St, Los Angeles',
  },
  {
    date: '2016-05-04',
    name: 'Tom',
    address: 'No. 189, Grove St, Los Angeles',
  },
  {
    date: '2016-05-01',
    name: 'Tom',
    address: 'No. 189, Grove St, Los Angeles',
  },
]

onMounted(() => {
  nextTick(() => {
    document.getElementById('scrollBox').addEventListener("scroll", onScroll);
  })
})

onBeforeUnmount(() => {
  document.getElementById('scrollBox').removeEventListener('scroll', onScroll)
})

const scrollToAnchor = (id, index) => {
 activeIndex.value = index
  var anchor = document.getElementById(id);
  var scrollBox = document.getElementById('scrollBox')
  nextTick(() => {
    scrollBox.scrollTo({
      top: anchor.offsetTop - 30,
      behavior: 'smooth'
    });
 })
  // 点击时暂停页面的滚动监听事件,防止点击时导航高亮出现走马灯效果;
  // 此处的定时器存在缺陷,点击完2秒之内滚动还是会偶现走马灯现象;
  isScroll.value = false
  let timeId = '';
  clearTimeout(timeId);
  timeId = setTimeout(() => {
    isScroll.value = true;
  }, 2000);
};

// 滚动监听器
const onScroll = ()=> {
  if(!isScroll.value) return
  // 获取所有锚点元素
  const navContents = document.querySelectorAll('.container .section')
  // 所有锚点元素的 offsetTop
  const offsetTopArr = []
  navContents.forEach(item => {
    offsetTopArr.push(item.offsetTop-30)
  })
  // 获取当前文档流的 scrollTop
  const scrollTop = document.getElementById('scrollBox').scrollTop
  // 定义当前点亮的导航下标
  let navIndex = 1
  for (let n = 1; n <= offsetTopArr.length; n++) {
    // 如果 scrollTop 大于等于第 n 个元素的 offsetTop 则说明 n-1 的内容已经完全不可见
    // 那么此时导航索引就应该是 n 了
    if (scrollTop >= offsetTopArr[n-1]) {
      navIndex = n
    }
  }
  // 把下标赋值给 vue 的 data
  activeIndex.value = navIndex
}

</script>
<style scoped lang="scss">
.box{
  // width: 98%;
  margin: auto;
  height: 100%;
  overflow: auto;
}
.fixed-box{
  width: calc(100% - 200px);
  background: white;
  position: fixed;
  top: 84px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  z-index: 99;
  padding: 5px 30px;
  box-sizing: border-box;
  box-shadow: 1px 1px 1px #ccc;
  span {
    padding: 0 10px;
    list-style: none;
  }
}
.container {
  padding-top: 15px;
  position: relative;
}
.section {
  margin: 20px 0;
}
:deep(.el-tabs ){
  --el-tabs-header-height: 50px;
}
.active_anchor {
  color: #409eff;
}
</style>


  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue锚点定位双向滚动是一种实现在页面中点击锚点链接时,页面能够滚动到对应位置的功能。这个功能可以通过监听滚动事件和使用ref属性来实现。首先,在左边的内容模块中给每个模块添加一个ref属性和相同的class类名,以便获取对应的DOM元素。然后,在内容区域添加滑动事件@scroll="handleScroll",当内容区域滑动时触发handleScroll方法。接下来,在右边的锚点链接中添加点击事件@click="goAnchor('anchor-' + index, index)",当点击锚点链接时,调用goAnchor方法获取对应的DOM元素。在goAnchor方法中,可以使用scrollIntoView方法将对应的DOM元素滚动到可视区域内实现双向滚动效果。这样,当点击锚点链接时,页面会滚动到对应的位置。 #### 引用[.reference_title] - *1* [vue:实现锚点双向滚动/文章章节联动滚动效果](https://blog.csdn.net/qq_36604536/article/details/126936016)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [vue 双向锚点实现 简易版(scrollIntoView)](https://blog.csdn.net/weixin_47978760/article/details/127808313)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值