一、 做出的效果图

在这里插入图片描述

二、用vue3和ant Design vue的锚点组件画出页面

<div class="main">
    <!-- 锚点组件 -->
    <a-anchor
      :affix="true"
      :targetOffset="10"
      @click="handleClick"
      @change="onChange"
      :getContainer="getContainer"
      :getCurrentAnchor="getCurrentAnchor"
      style="float: right;"
    >
      <a-anchor-link
        v-for="item in state.timeData"
        :key="item.id"
        class="navLink"
        :class="{ 'active': activeLink == item.id }"
        :href="`#${item.id}`"
        :title="item.title"
      />
    </a-anchor>
    <!-- 页面内容 -->
    <div class="content">
      <div style="font-size:2rem;margin:10px 0;">账号与设置功能更新日志</div>
      <div ref="containerRef">
        <div class="container" v-for="(item, index) in state.timeData" :key="index" :id="item.id">
          <h2>{{ item.title }}</h2>
          <h2>{{ item.name }}</h2>
          <h3>{{ item.content }}</h3>
        </div>
      </div>
    </div>
  </div>

三、用ts写监听以及定位逻辑

1. 定义数据
const state = reactive({
  timeData: [
    {
      id: 0,
      title: '2024.11.1更新',
      name: '安卓设备支持系统通行密钥',
      content: '使用安卓设备设置通行密钥时,支持设置系统通行密钥,实现在同品牌设备间共享通行密钥。',
    },
    {
      id: 1,
      title: '2024.11.2更新',
      name: '桌面端快捷键更新',
      content: '系统支持查看快捷键列表,并按需修改、重置快捷键。',
    },
    {
      id: 2,
      title: '2024.11.3更新',
      name: '密码设置调整',
      content: '在注册或登录账号时,如尚未设置密码,为了提升账号安全,同时防止因无法获取验证码导致无法登录,需完成密码设置。',
    },
    {
      id: 3,
      title: '2024.11.4更新',
      name: '注册流程更新',
      content:
        '注册时,你可使用手机号注册飞书账号,创建你的飞书企业或组织,或加入其他企业或组织,与所在企业同事或组织成员在线沟通、协作编辑文档、共享日程并快速组织会议等,实现高效办公协作。如需注册用于个人使用场景的账号,可在注册流程中的 使用场景 中选择 个人使用,根据实际情况选择 你的职位,然后点击 创建企业或组织,并填写信息。其中,企业或组织名称、行业类型、人员规模 和 所在地区 可任意填写或选择。完成后点击 创建,即可成功注册账号.',
    },
    {
      id: 4,
      title: '2024.11.5更新',
      name: '通行密钥',
      content:
        '通行密钥支持全方位的身份验证技术,用户可以将每台手机或电脑等设备的屏幕解锁方式(如:面容识别、指纹识别、锁屏密码等)作为通行密钥,添加完成后,可以通过此方式安全便捷地完成登录或身份验证等操作。',
    },
  ],
});
2. 写出点击锚点导航跳转到页面相应的内容
import { LinkData } from './modal';

const activeLink = ref('');

// 指定滚动的容器
const getContainer = () => {
  // 给组件指定渲染的容器,解决锚点不会随页面滚动而移动的问题
  return document.querySelector('.container');
};
// 自定义高亮锚点
const getCurrentAnchor = () => {}
//锚点跳转实现方法
const handleClick = (e: Event, link: LinkData) => {
  e.preventDefault();
  if (link.href) {
    // 找到锚点对应的节点
    let element = document.getElementById(link.href.replace('#', ''));
    activeLink.value = link.href.slice(1);
    // 如果对应id的锚点存在,就跳滚动到锚点顶部
    if (element) {
      element.scrollIntoView({
        behavior: 'smooth',
      });
    }
  }
};
// 当滑动页面时,监听统一页面锚点与导航的定位
const onChange = (link: string) => {
  activeLink.value = link;
};
3. 写出监听滚动页面时,自动滑动导航的锚点定位
import { debounce } from './debounce '; // 引入防抖函数

let requestAnimationFrameId: number | null = null;
let scrollTimeout;


// 监听滚动页面定位导航
const checkActiveLink = () => {
  // 获取页面内容的容器
  const sections = document.querySelectorAll('.container');
  let current = '';
  sections.forEach((section) => {
    // offsetTop返回当前元素相对于其父元素的顶部内边距的距离。
    const sectionTop = section.offsetTop;
    // 返回元素的可视高度,包括内边距(padding),但不包括边框(border)、滚动条或外边距(margin)
    const sectionHeight = section.clientHeight;
    // 获取滚动的高度
    const scrollTop = document.documentElement.scrollTop;
    // 计算元素到窗口顶端的距离
    const rect = section.getBoundingClientRect();
    const distance = rect.top;
    // console.log("---111",scrollTop,"---222",sectionTop,"---333",sectionHeight,'---444',distance);
    
    // 当滚动的高度在锚点的页面内容的元素范围之内,并且元素距离窗口的距离超出窗口时成立
    if (scrollTop <= (sectionTop + sectionHeight + 5) && distance <= 5 ) {
      // 获得到元素的id
      current = section.id;
    }
  });
  // 将元素id复制给activeLink,来控制选中锚点的样式。
  activeLink.value = current;
};
const debouncedCheckActiveLink = debounce(checkActiveLink, 100, { leading: true, trailing: true });

onMounted(() => {
  // targetOffset.value = window.innerHeight ;
  window.addEventListener('scroll', ()=>{
    // 清除之前的定时器
      clearTimeout(scrollTimeout);
      // 设置一个新的定时器,在200毫秒后如果没有新的滚动事件,则触发处理函数
      scrollTimeout = setTimeout(debouncedCheckActiveLink, 100);
  })
});
onUnmounted(() => {
  window.removeEventListener('scroll', debouncedCheckActiveLink);
  if (requestAnimationFrameId !== null) {
    cancelAnimationFrame(requestAnimationFrameId);
  }
});
注: 滑动页面时触发多次滚动事件,我们需要做防抖。并且我们需要监听的是滚动之后的数据(滚动一次页面的高度;内容元素距离父元素的距离;内容元素的可视高度;滚动之后内容元素距离窗口顶端的距离),所以我们需要用setTimeout来延迟滚动之后的事件,等100ms之后没有任何滚动动作之后再触发事件。
4. 防抖函数debounced
export function debounce<T extends (...args: any[]) => any>(
  func: T,
  wait: number,
  options: { leading?: boolean; trailing?: boolean } = {}
): (...args: Parameters<T>) => void {
  let timeout: NodeJS.Timeout | null = null;
  let result: ReturnType<T> | undefined;
  let context: any;
  let args: Parameters<T>;

  const later = () => {
    timeout = null;
    if (!options.leading) {
      result = func.apply(context, args);
    }
  };

  return function (this: any, ..._args: Parameters<T>): void {
    context = this;
    args = _args;

    if (timeout === null) {
      if (options.leading) {
        result = func.apply(context, args);
        timeout = setTimeout(later, wait);
      } else {
        timeout = setTimeout(later, wait);
      }
    } else if (options.trailing) {
      clearTimeout(timeout);
      timeout = setTimeout(later, wait);
    }
  };
}
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐