vue3简单写导航anchor示例(支持点击高亮和滚动判断高亮)

news2024/11/25 15:48:57

1.  点击anchor, 相应的anchorlink高亮

function anchorClick(index) {
  forceStop.value = true;
  time = Date.now();
  wheelRef.value.children[index].scrollIntoView({
    block: 'start',
    behavior: 'smooth'
  });
  // 给一些延时, 再点亮anchor, 同时不再限制scroll事件函数里面滚动高亮的判断
  setTimeout(() => {
    forceStop.value = false;
    time = null;
    currentIndex.value = index;
  }, 100 * Math.abs(currentIndex.value - index) > 1000
    ? 1000
    : 100 * Math.abs(currentIndex.value - index));
}

2. scroll页面, 根据列表的容器高度和滚动块之间的数值关系判断anchor高亮:

//滚动的函数
function handleScroll(e) {
  time && console.log((Date.now() - time) / 1000, '滚动间隔时间', forceStop.value)
  if (forceStop.value) {
    return;
  }
  const scrollingElement = e.target;
  const scrollTop = scrollingElement.scrollTop;
  const headerOffsetTop = headerRef.value.offsetTop;
  const headerOffsetHeight = headerRef.value.offsetHeight;
  const navOffsetTop = navRef.value.offsetTop;
  const navOffsetHeight = navRef.value.offsetHeight;
  const windowClientHeight = scrollingElement.clientHeight;
  const windowScrollHeight = scrollingElement.scrollHeight;
  // 如果滚动元素的scrollTop比header元素的高度+offsetTop还大, 就让nav部分悬停在顶部!!!
  if (scrollTop >= headerOffsetHeight + headerOffsetTop) {
    // 因为nav悬停了, 所以scrollTop - header的高度就是判断靠近顶部窗口的可见的list内容了, 从而和anchorlink的高亮产生联系
    const gap = scrollTop - headerOffsetHeight;
    const idx = _.findIndex(listData1, ee => {
      const a = _.get(ee, 'listItemsHeightArrs');
      if (gap >= a[0] && gap < a[1]) {
        return ee
      }
    })
    currentIndex.value = idx;
    isFixed.value = true;
  } else {
    isFixed.value = false;
    currentIndex.value = 0;
  }
  // 滑到底部
  if (windowClientHeight + scrollTop === windowScrollHeight) {
    currentIndex.value = listData1.length - 1;
  }
}

3. 完整示例代码:

<template>
  <div class="fixed-top-container" :ref="scrollWrapperRef">
    <header class="header" :ref="headerRef">头部</header>

    <nav class="fixed-top-nav" :ref="navRef" :class="{ isFixed }">
      <div class="box" v-for="(item, index) in navData" :key="index">
        {{ item.title }}
      </div>
    </nav>
    <ul class="fixed-top-list" :ref="wheelRef">
      <li v-for="(item, index) in listData1">
        {{ item.name }}
        <ul>
          <li class="list-item" v-for="(item, index) in item.list">{{ item.text }}</li>
        </ul>
      </li>
    </ul>

    <ul class="anchor-conatiner">
      <li v-for="(item, index) in listData1" :class="currentIndex === index ? 'current' : ''" @click="anchorClick(index)">
        {{ item.name }}</li>
    </ul>

  </div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted, onBeforeUnmount, nextTick, Ref } from 'vue';
import _ from 'lodash';

const isFixed = ref(false); //是否固定的
const headerRef = ref('headerRef') as Ref;
const navRef = ref('navRef') as Ref;
const wheelRef = ref('wheelRef') as Ref;
const currentIndex = ref(0);
const forceStop = ref(false);
const scrollWrapperRef = ref('scrollWrapperRef') as Ref;
let time: any = null

// mock数据-----------------------start--------------
const navData = reactive([
  { title: 'navRef', id: 1 },
  { title: 'nav2', id: 2 },
  { title: 'nav3', id: 3 },
  { title: 'nav4', id: 4 },
]);

const arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N'];
let sum = 0;
const listData1 = reactive(Array.from({ length: arr.length }, (item, index) => {
  const list = Array.from({ length: 5 }, (item, i) => ({
    id: 'list-item-' + i + 1,
    text: 'list-item-text-' + i,
    name: 'list-name-' + i,
  }));
  const sum1 = sum
  sum += 40 * (list.length + 1)
  return {
    listItemsHeightArrs: [sum1, sum], // 前一个标题内的list内容累计高度, 当前标题内的内容累计高度
    name: arr[index] + '-累计高度为:' + JSON.stringify([sum1, sum]),
    list,
  }
}));
// mock数据-----------------------end--------------

function anchorClick(index) {
  forceStop.value = true;
  time = Date.now();
  wheelRef.value.children[index].scrollIntoView({
    block: 'start',
    behavior: 'smooth'
  });
  // 给一些延时, 再点亮anchor, 同时不再限制scroll事件函数里面滚动高亮的判断
  setTimeout(() => {
    forceStop.value = false;
    time = null;
    currentIndex.value = index;
  }, 100 * Math.abs(currentIndex.value - index) > 1000
    ? 1000
    : 100 * Math.abs(currentIndex.value - index));
}


//滚动的函数
function handleScroll(e) {
  time && console.log((Date.now() - time) / 1000, '滚动间隔时间', forceStop.value)
  if (forceStop.value) {
    return;
  }
  const scrollingElement = e.target;
  const scrollTop = scrollingElement.scrollTop;
  const headerOffsetTop = headerRef.value.offsetTop;
  const headerOffsetHeight = headerRef.value.offsetHeight;
  const navOffsetHeight = navRef.value.offsetHeight;
  const windowClientHeight = scrollingElement.clientHeight;
  const windowScrollHeight = scrollingElement.scrollHeight;
  // 如果滚动元素的scrollTop比header元素的高度+offsetTop还大, 就让nav部分悬停在顶部!!!
  if (scrollTop >= headerOffsetHeight + headerOffsetTop) {
    // 因为nav悬停了, 所以scrollTop - header的高度就是判断靠近顶部窗口的可见的list内容了, 从而和anchorlink的高亮产生联系
    const gap = scrollTop - headerOffsetHeight;
    const idx = _.findIndex(listData1, ee => {
      const a = _.get(ee, 'listItemsHeightArrs');
      if (gap >= a[0] && gap < a[1]) {
        return ee
      }
    })
    currentIndex.value = idx;
    isFixed.value = true;
  } else {
    isFixed.value = false;
    currentIndex.value = 0;
  }
  // 滑到底部
  if (windowClientHeight + scrollTop === windowScrollHeight) {
    currentIndex.value = listData1.length - 1;
  }
}


onMounted(() => {
  nextTick(() => {
    scrollWrapperRef.value.addEventListener('scroll', handleScroll, false);
  });
})

onBeforeUnmount(() => { // 页面即将销毁取消事件监听
  scrollWrapperRef.value.removeEventListener('scroll', handleScroll);
})
</script>
<style scoped lang="scss">
* {
  margin: 0;
  padding: 0;
}

.fixed-top-container {
  height: 100vh;
  overflow: auto;

  & .header {
    height: 200px;
    width: 100%;
    background-color: #ff5555;
  }

  & .fixed-top-nav {
    display: flex;
    width: 100%;
    background-color: #f90;

    &.isFixed {
      position: fixed;
      left: 0;
      top: 0;
      z-index: 999;
    }

    & .box {
      font-size: 14px;
      height: 30px;
      line-height: 30px;
      color: #333;
      flex: 1 1 0%;
    }
  }

  & .fixed-top-list {
    list-style: none;
    font-size: 16px;
    line-height: 40px;


    &>li {
      background-color: green;
    }

    & li {
      box-sizing: border-box;
    }

    & .list-item {
      width: 100%;
      height: 40px;
      line-height: 40px;
      font-size: 16px;
      border-bottom: 1px solid #333;
      background-color: #fff;
    }
  }

  .anchor-conatiner {
    position: fixed;
    top: 10%;
    right: 10px;

    & li {
      font-size: 14px;

      &.current {
        color: red;
      }

      &.light {
        color: green;
      }
    }
  }
}
</style>

4. 如果不让nav部分悬停:

<template>
  <div class="fixed-top-container" :ref="scrollWrapperRef">
    <header class="header" :ref="headerRef">头部</header>

    <nav class="fixed-top-nav" :ref="navRef">
      <div class="box" v-for="(item, index) in navData" :key="index">
        {{ item.title }}
      </div>
    </nav>
    <ul class="fixed-top-list" :ref="wheelRef">
      <li v-for="(item, index) in listData1">
        {{ item.name }}
        <ul>
          <li class="list-item" v-for="(item, index) in item.list">{{ item.text }}</li>
        </ul>
      </li>
    </ul>

    <ul class="anchor-conatiner">
      <li v-for="(item, index) in listData1" :class="currentIndex === index ? 'current' : ''" @click="anchorClick(index)">
        {{ item.name }}</li>
    </ul>

  </div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted, onBeforeUnmount, nextTick, Ref } from 'vue';
import _ from 'lodash';

const headerRef = ref('headerRef') as Ref;
const navRef = ref('navRef') as Ref;
const wheelRef = ref('wheelRef') as Ref;
const currentIndex = ref(0);
const forceStop = ref(false);
const scrollWrapperRef = ref('scrollWrapperRef') as Ref;
let time: any = null

// mock数据-----------------------start--------------
const navData = reactive([
  { title: 'navRef', id: 1 },
  { title: 'nav2', id: 2 },
  { title: 'nav3', id: 3 },
  { title: 'nav4', id: 4 },
]);

const arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N'];
let sum = 0;
const listData1 = reactive(Array.from({ length: arr.length }, (item, index) => {
  const list = Array.from({ length: 5 }, (item, i) => ({
    id: 'list-item-' + i + 1,
    text: 'list-item-text-' + i,
    name: 'list-name-' + i,
  }));
  const sum1 = sum
  sum += 40 * (list.length + 1)
  return {
    listItemsHeightArrs: [sum1, sum], // 前一个标题内的list内容累计高度, 当前标题内的内容累计高度
    name: arr[index] + '-累计高度为:' + JSON.stringify([sum1, sum]),
    list,
  }
}));
// mock数据-----------------------end--------------

function anchorClick(index) {
  forceStop.value = true;
  time = Date.now();
  wheelRef.value.children[index].scrollIntoView({
    block: 'start',
    behavior: 'smooth'
  });
  // 给一些延时, 再点亮anchor, 同时不再限制scroll事件函数里面滚动高亮的判断
  setTimeout(() => {
    forceStop.value = false;
    time = null;
    currentIndex.value = index;
  }, 100 * Math.abs(currentIndex.value - index) > 1000
    ? 1000
    : 100 * Math.abs(currentIndex.value - index));
}


//滚动的函数
function handleScroll(e) {
  time && console.log((Date.now() - time) / 1000, '滚动间隔时间', forceStop.value)
  if (forceStop.value) {
    return;
  }
  const scrollingElement = e.target;
  const scrollTop = scrollingElement.scrollTop;
  const headerOffsetTop = headerRef.value.offsetTop;
  const headerOffsetHeight = headerRef.value.offsetHeight;
  const navOffsetTop = navRef.value.offsetTop;
  const navOffsetHeight = navRef.value.offsetHeight;
  const windowClientHeight = scrollingElement.clientHeight;
  const windowScrollHeight = scrollingElement.scrollHeight;
  // navOffsetTop-headerOffsetTop就是header的高度, 还需要加上nav的高度才是list内容上面的块的高度
  const gap = scrollTop - (navOffsetTop-headerOffsetTop+navOffsetHeight);
  if (gap >= 0) {
    const idx = _.findIndex(listData1, ee => {
      const a = _.get(ee, 'listItemsHeightArrs');
      if (gap >= a[0] && gap < a[1]) {
        return ee
      }
    })
    currentIndex.value = idx;
  }
  else {
    currentIndex.value = 0;
  }
  // 滑到底部
  if (windowClientHeight + scrollTop === windowScrollHeight) {
    currentIndex.value = listData1.length - 1;
  }
}


onMounted(() => {
  nextTick(() => {
    scrollWrapperRef.value.addEventListener('scroll', handleScroll, false);
  });
})

onBeforeUnmount(() => { // 页面即将销毁取消事件监听
  scrollWrapperRef.value.removeEventListener('scroll', handleScroll);
})
</script>
<style scoped lang="scss">
* {
  margin: 0;
  padding: 0;
}

.fixed-top-container {
  height: 100vh;
  overflow: auto;

  & .header {
    height: 200px;
    width: 100%;
    background-color: #ff5555;
  }

  & .fixed-top-nav {
    display: flex;
    width: 100%;
    background-color: #f90;

    & .box {
      font-size: 14px;
      height: 30px;
      line-height: 30px;
      color: #333;
      flex: 1 1 0%;
    }
  }

  & .fixed-top-list {
    list-style: none;
    font-size: 16px;
    line-height: 40px;


    &>li {
      background-color: green;
    }

    & li {
      box-sizing: border-box;
    }

    & .list-item {
      width: 100%;
      height: 40px;
      line-height: 40px;
      font-size: 16px;
      border-bottom: 1px solid #333;
      background-color: #fff;
    }
  }

  .anchor-conatiner {
    position: fixed;
    top: 10%;
    right: 10px;

    & li {
      font-size: 14px;

      &.current {
        color: red;
      }

      &.light {
        color: green;
      }
    }
  }
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1161336.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【教3妹学编辑-算法题】环和杆

3妹&#xff1a;2哥&#xff0c;今年春节的放假安排出来了&#xff0c;今年春节放8天假&#xff0c;我们公司除夕提前放一天&#xff0c;总共9天假。 耶~~~ 2哥 :你们公司这么好啊&#xff0c; 我们公司的放假安排还没出来&#xff0c;不知道今年除夕能不能回家了… 3妹&#x…

(免费领源码)Java#MYSQL超市管理系统10428-计算机毕业设计项目选题推荐

目 录 摘要 1 绪论 1.1 研究意义 1.2国内外研究现状 1.3论文结构与章节安排 2 超市管理系统系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1 数据流程 3.3.2 业务流程 2.3 系统功能分析 2.3.1 功能性分析 2.3.2 非功能性分析 2.4 系统用例分析 2.5本章小结 3 …

openGauss-向量化执行引擎-VecUnique算子

openGauss-向量化执行引擎系列-VecUnique算子 openGauss实现了向量化执行引擎&#xff0c;达到算子级别的并行。也就是说在执行器火山模型基础上&#xff0c;一次处理一批数据&#xff0c;而不是一次一个元组。这样可以充分利用SIMD指令进行优化&#xff0c;达到指令级别并行。…

知识点滴 - 纸张的大小

关于纸张大小的描述方法 纸张大小的描述方法主要有两种&#xff0c;一种是我们经常使用的打印纸使用的A4、A3的叫法&#xff0c;这个是国际标准。 另一种是开本的叫法&#xff0c;比如16开、32开等。开本常见于书籍印刷品的规格。正规出版物基本没有使用 A4&#xff08;ISO国际…

1688阿里巴巴官方开放平台API接口获取跨境属性、跨境包裹重量、单位重量等参数调用示例说明

1688.item_get_specifications-获得跨境属性 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;注册调用key接入secretString是调用密钥api_nameString是API接口名称&#xff08;包括在请求地址中&#xff09;[item_search,item_…

2023SHCTF web方向wp

1.ezphp 看一眼&#xff0c;你大爷&#xff0c;啥玩意都给我过滤完了。 还好下面有preg_replace()/e&#xff0c;会把replacement当作php语句执行 传参pattern.*&#xff0c; .*表示任意字符&#xff0c;code{${phpinfo()}} &#xff0c;为什么这样写&#xff0c;因为,print_…

CHS零壹视频恢复程序高级版视频修复OCR使用方法

目前CHS零壹视频恢复程序监控版、专业版、高级版已经支持了OCR&#xff0c;OCR是一种光学识别系统&#xff0c;高级版最新版本中不仅仅是在视频恢复中支持OCR&#xff0c;同时视频修复模块也增加了OCR功能&#xff0c;此功能可以针对一些批量修复的视频文件&#xff08;如执法仪…

网络编程---Socket

文章目录 网络编程基础什么是网络编程&#xff1f;网络编程的基本概念 网络编程实现Socket套接字UDP网络通信流程&#xff08;回显服务器&#xff09;服务器&#xff1a;客户端完整代码示例&#xff1a; TCP网络通信流程&#xff08;回显服务器&#xff09;服务器客户端完整的代…

医保经办系统练兵比武竞赛中用到的软件和硬件

全国医保经办系统练兵比武竞赛包括必答题、抢答题、案例题、实操题、风险题&#xff0c;用到选手端平板、评委端平板、主持人平板、抢答器等设备。分别计算团队分和个人分。答题规则和计分方案均较为复杂&#xff0c;一般竞赛软件无法实现&#xff0c;要用到高端竞赛软件&#…

谷歌动态搜索广告被滥用引发恶意软件泛滥

研究人员发现了一种新方法&#xff0c;可以利用易受攻击的网站向搜索引擎用户发送恶意的、有针对性的广告&#xff0c;这种方法能够传播大量恶意软件&#xff0c;使受害者完全不知所措。 关键是“动态搜索广告”&#xff0c;谷歌利用网站登陆页面的内容将目标广告与搜索配对的…

Kubernetes 高级调度 - Affinity

Author&#xff1a;rab 目录 前言一、Node 亲和性1.1 NodeAffinity1.1.1 Hard Node Affinity1.1.2 Soft Node Affinity 1.2 NodeAntiAffinity 二、Pod 亲和性2.1 PodAffinity2.1.1 Hard Pod Affinity2.1.2 Soft Pod Affinity 2.2 PodAntiAffinity 总结 前言 Kubernetes 中的 A…

OceanBase:02-单机部署(生产环境)

目录 一、部署规划 二、配置要求 三、部署前配置 1.配置 limits.conf 2.配置 sysctl.conf 3.关闭防火墙 4.关闭 SELinux 5.创建数据目录&#xff0c;修改文件所有者信息 6.设置无密码 SSH 登录 7.安装jdk 四、解压执行安装 五、OBD命令行部署 1.修改配置文件(all-c…

网络质量探测

目录 一.BFD监测网络状态 二. NQA检测网络状态 一.BFD监测网络状态 BFD(BidrectionaL Forwarding Detection 双向转发检测)用于快速检测系统设备之间的发送和接受两个方向的通信故障&#xff0c;并在出现故障时通知生成应用。BFD 广泛用于链路故障检测&#xff0c;并能实现与…

探究Java虚拟机运行时数据区,了解方法区的奥秘

目录 一、栈、堆、方法区交互关系 二、方法区的理解 三、HotSpot中方法区的演进 四、设置方法区大小与OOM 五、如何解决OOM 六、方法区的内部结构 &#xff08;一&#xff09;类型变量 &#xff08;二&#xff09;域信息 &#xff08;三&#xff09;方法信息 &#x…

AN动画基础——遮罩动画

【AN动画基础——遮罩动画】 什么是遮罩动画基本使用方法实战&#xff1a;水墨遮罩 本篇内容&#xff1a;了解遮罩动画 重点内容&#xff1a;遮罩动画应用 工 具&#xff1a;Adobe Animate 2022 什么是遮罩动画 遮罩动画是一种常见的图形效果&#xff0c;利用遮罩层来实现元素…

ARPG----C++学习记录02 Section6位置,偏移,函数

设置actor位置 这一句代码就可以更改位置和旋转 给位置添加偏移offset 将debug的持久都设置为false,在tick中调用&#xff0c;球就会动。这是每帧移动&#xff0c;所以移动速度和帧率有关&#xff0c;需要更改 void Aitem::Tick(float DeltaTime) {Super::Tick(DeltaTime);Ad…

【洛谷算法题】P5710-数的性质【入门2分支结构】

&#x1f468;‍&#x1f4bb;博客主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 收录于专栏 【洛谷算法题】 文章目录 【洛谷算法题】P5710-数的性质【入门2分支结构】&#x1f30f;题目描述&#x1f30f;输入格式&a…

Nginx搭配负载均衡和动静分离:构建高性能Web应用的完美组合

前言 在当今互联网时代&#xff0c;高并发访问已成为Web应用程序面临的重要挑战之一。为了保证系统的稳定性和用户体验&#xff0c;我们需要采取有效的措施来应对这一挑战。本文将介绍如何利用Nginx搭配负载均衡和动静分离技术&#xff0c;构建高性能的Web应用。 一、Nginx简…

java反射API

反射 什么是Java的动态机制什么是反射机制什么是Class类Class提供了诸多的get方法 反射机制实例化对象Class提供了一个方法 Constructor类指定构造器实例化对象 Method类获取一个类中的所有方法Class类提供了对应的方法 获取本类自定义的所有方法Class类提供了对应的方法 获取表…

第四章 应用SysML基本特性集的汽车示例 P2(断更)|系统建模语言SysML实用指南学习

仅供个人学习 使用试用版CSM很鸡肋&#xff0c;然后书中一些内容没有说明&#xff0c;自定义方面有点困难&#xff0c;第四章暂时停止 同时感觉画图的顺序也很随意&#xff1f;甚至需求图放在了后面&#xff0c;觉得很离谱。 准备跳过这一章节 汽车模型 续P1 序列图表示启…