vue 实现滚动导航

效果

在这里插入图片描述

实现

css 使用了 unocss

使界面滚动到给定元素的指定坐标位置

window.scrollTo({ top: 0 })

使用了内边距避免最后数据高度不够

<main class="pb-100vh"></main>

完整代码

<script lang="ts" setup>
defineOptions({ name: 'DemoView' })
/** dom */
const itemRefs = ref<HTMLElement[]>([])

/** 导航列表数据 */
const navList = ['导航一', '导航二', '导航三', '导航四', '导航五']

/** 导航的索引 */
const activeIndex = ref(0)

/**
 * 切换导航
 * @param index 点击的索引
 */
function handleNav(index: number) {
  scrollTo({ top: itemRefs.value[index]?.offsetTop })
}

/**
 * 监听滚动方法
 */
function onScroll() {
  const offsetTopArr: number[] = []
  itemRefs.value?.forEach((item) => {
    offsetTopArr.push((item as HTMLElement)?.offsetTop)
  })

  const scrollTop = document.documentElement.scrollTop || document.body.scrollTop

  let navIndex = 0
  for (let n = 0; n < offsetTopArr.length; n++) {
    // 如果 scrollTop 大于等于第n个元素的 offsetTop 则说明 n-1 的内容已经完全不可见
    // 那么此时导航索引就应该是n了
    if (scrollTop >= offsetTopArr[n]) {
      navIndex = n
    }
  }
  activeIndex.value = navIndex
}

onMounted(() => {
  window.addEventListener('scroll', onScroll, false)
})
onUnmounted(() => {
  window.removeEventListener('scroll', onScroll)
})

/**
 * 封装滚动到指定位置的方法
 * @param param0
 */
function scrollTo({
  top = 0,
  behavior = 'smooth'
}: {
  top?: number | undefined
  behavior?: ScrollBehavior
}) {
  window.scrollTo({
    top,
    behavior
  })
}
</script>

<template>
  <div class="pl-150px">
    <nav class="fixed left-0px top-80px">
      <ul>
        <template v-for="(nav, index) of navList" :key="index">
          <li
            class="py-30px text-[#666] cursor-pointer"
            :class="{ active: activeIndex === index }"
            @click="handleNav(index)"
          >
            {{ nav }}
          </li>
        </template>
      </ul>
    </nav>

    <main class="pb-100vh">
      <template v-for="(nav, index) of navList" :key="index">
        <div ref="itemRefs" class="h-200px w-200px mb-20px pb-30px bg-blue-700 text-white">
          {{ nav }}
        </div>
      </template>
    </main>
  </div>
</template>

<style lang="scss" scoped>
li {
  list-style: none;
}

.active {
  @apply text-blue-700 font-bold;
}
</style>

滚动内容不在最顶部,而是有其他内容。存在导航等

在这里插入图片描述
composables/use-scroll.ts

export function useScroll(height: number = 60) {
  /** dom 列表 */
  const itemRefs = ref<HTMLElement[]>([])

  /** 导航的索引 */
  const activeIndex = ref(0)

  /**
   * 切换导航
   * @param index 点击的索引
   */
  function handleNav(index: number) {
    scrollTo({ top: itemRefs.value[index]?.offsetTop - height })
  }

  /**
   * 监听滚动方法
   */
  function onScroll() {
    /** 存储每一项距离顶部的高度 */
    const offsetTopArr: number[] = []
    itemRefs.value?.forEach((item) => {
      offsetTopArr.push((item as HTMLElement)?.offsetTop)
    })

    /** 滚动距离 */
    const scrollTop = (document.documentElement.scrollTop || document.body.scrollTop) + height

    let navIndex = 0
    for (let n = 0; n < offsetTopArr.length; n++) {
      // 如果 scrollTop 大于等于第n个元素的 offsetTop 则说明 n-1 的内容已经完全不可见
      // 那么此时导航索引就应该是n了
      if (scrollTop >= offsetTopArr[n]) {
        navIndex = n
      }
    }
    /** 统一在此设置导航索引 */
    activeIndex.value = navIndex
  }

  onMounted(() => {
    window.addEventListener('scroll', onScroll, false)
  })
  onUnmounted(() => {
    window.removeEventListener('scroll', onScroll)
  })

  /**
   * 封装滚动到指定位置的方法
   * @param param0
   */
  function scrollTo({
    top = 0,
    behavior = 'smooth'
  }: {
    top?: number | undefined
    behavior?: ScrollBehavior
  }) {
    window.scrollTo({
      top,
      behavior
    })
  }
  return {
    activeIndex,
    itemRefs,
    handleNav
  }
}

index.vue

<script lang="ts" setup>
import { useScroll } from './composables/use-scroll'

defineOptions({ name: 'DemoView' })

/** 导航高度 */
const HEIGHT = 60

/** 导航列表数据 */
const navList = ['导航一', '导航二', '导航三', '导航四', '导航五']

const { activeIndex, itemRefs, handleNav } = useScroll(HEIGHT)
</script>

<template>
  <div>
    <nav class="sticky top-0px bg-white shadow">
      <ul class="flex justify-evenly items-center h-60px">
        <template v-for="(nav, index) of navList" :key="index">
          <li
            class="text-[#666] cursor-pointer"
            :class="{ active: activeIndex === index }"
            @click="handleNav(index)"
          >
            {{ nav }}
          </li>
        </template>
      </ul>
    </nav>

    <main class="flex flex-col justify-center items-center">
      <template v-for="(nav, index) of navList" :key="index">
        <div ref="itemRefs" class="h-200px w-200px mb-20px pb-30px bg-blue-700 text-white">
          {{ nav }}
        </div>
      </template>
    </main>
  </div>
</template>

<style lang="scss" scoped>
li {
  list-style: none;
}

.active {
  @apply text-blue-700 font-bold;
}
</style>

  • 12
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值