目录锚点Demo

简介: 目录锚点Demo

title: 目录锚点Demo

tags:

  • js
    categories:
  • JavaScript
    abbrlink: e986e4b8
    date: 2023-02-19 22:14:53


目录锚点Demo


修改自参考文章1,添加一些注释,精简了一些内容


HTML

    <div class="container">
        <!-- 内容区域 -->
      <div class="content">
        <div data-tab="districtName1" class="districtName">
          <h2 class="data-tab">页面一</h2>
          <div>
            <ul>
                <li>当标题滚动到顶部时, 切换到当前目录</li>
            </ul>
          </div>
        </div>
        <div data-tab="districtName2" class="districtName">
          <h2 class="data-tab">页面二</h2>
          <div>页面二内容...</div>
        </div>
        <div data-tab="districtName3" class="districtName">
          <h2 class="data-tab">页面三</h2>
          <div>页面三内容...</div>
        </div>
        <div data-tab="districtName4" class="districtName">
          <h2 class="data-tab">页面四</h2>
          <div>页面四内容...</div>
        </div>
        <div data-tab="districtName5" class="districtName">
          <h2 class="data-tab">页面五</h2>
          <div>页面五内容...</div>
        </div>
      </div>
      <!-- 目录容器 -->
      <div class="directory">
        <div id="category">
          <ul></ul>
        </div>
      </div>
    </div>
    <!-- 底部填充物, 避免无法滑到最后一个目录 -->
    <div style="height: 500px; background: #eee"></div>


CSS

    html,
      body {
        margin: 0;
        padding: 0;
        height: 100%;
      }

      .container {
        width: 100%;
        display: flex;
        padding: 20px 50px;
        box-sizing: border-box;
      }

      .directory {
        width: 200px;
      }

      .content {
        flex: 1;   
        display: flex;
        flex-direction: column;
      }

      .content h2 {
        margin: 0;
      }

      .districtName {
        width: 100%;
      }
      .districtName div {
        height: 500px;
        border: 1px solid #eee;
      }

      .container .directory ul,
      .container .directory li {
        list-style: none;
      }

      #category {
        /*粘性定位*/
        position: sticky;
        top: 16px;
        right: 0;
      }

      #category a {
        padding: 0 16px;
        color: #2d2e2f;
        outline: none;
        text-decoration: none;
        line-height: 30px;
      }

      #category a:hover {
        background: #efefef;
        cursor: pointer;
      }

      #category a.active {
        background: #efefef;
        color: #1890ff;
      }

JavaScript


  • 根据类名class="data-tab"生成目录,
  • 为每个目录添加点击事件,点击滚动到目标内容,(根据data-tab的属性值,目录与内容的data-tab属性一一对应,类似id吧)
  • 添加全局滚动监听事件, 触发时遍历内容, 判断哪个内容元素到顶部的距离小于100, 并激活内容元素对应的目录
  • 目录激活通过添加类名class=active实现
  • 滚动监听需获取两个值scrollTop和offsetTop
  • scrollTop指当前可视窗口滚动的距离,向下滚动逐渐增大
  • offsetTop指当前元素相对于其 offsetParent 元素的顶部内边距的距离,这里是class="districtName"的div容器距离class="container"的div容器的内边距,所以其每个元素的offsetTop是一个定值,并不随滚动而改变
  • 参考文章2中有画图解释,或者MDN中offsetTop , scrollTop
  • scrollIntoView滚动目标元素的父元素,使目标元素可见。
    class dataScroll {
      // 获取内容节点数组
      districtName = document.querySelectorAll(".container .districtName");
      // 目录容器
      directoryDom = document.querySelector(".directory #category ul");
      constructor() {
        this.initDom(); //初始化目录dom
        this.initScroll(); //初始化监听滚动
      }
      initDom() {
        let Listdom = "";
        // Element.innerHTML 属性设置或获取 HTML 语法表示的元素的后代。
        this.districtName.forEach((item) => {
          const linkClass = item.querySelector(".data-tab");
          Listdom += `<li><a data-tab=${item.getAttribute("data-tab")}>${linkClass.innerHTML}</a></li>`;
        });
        this.directoryDom.innerHTML = Listdom;
        // 给所有目录节点添加点击事件
        const Alist = this.directoryDom.querySelectorAll("li a");
        Alist.forEach((el) => {
          el.addEventListener("click", (e) => {
            // 遍历所有内容节点
            this.districtName.forEach((targetNode) => {
              if (targetNode.getAttribute("data-tab") === e.target.getAttribute("data-tab")) {
                //页面直接滚动不过度 A标签高亮
                // document.documentElement.scrollTop = targetNode.offsetTop

                // 页面动画滚动过度 A标签高亮
                // Element 接口的 scrollIntoView() 方法会滚动元素的父容器,使被调用 scrollIntoView() 的元素对用户可见。
                // https://developer.mozilla.org/zh-CN/docs/Web/API/Element/scrollIntoView
                targetNode.scrollIntoView({
                  behavior: "smooth",
                  block: "start",
                  inline: "start",
                });
              }
            });
          });
        });
      }
      initScroll() {
        // 添加滚动监听
        window.addEventListener("scroll", (e) => {
          // Document.documentElement 是一个会返回文档对象(document)的根元素的只读属性(如 HTML 文档的 <html> 元素)。
          // Document.body 表示当前文档中的 <body> 或 <frameset> 元素,或 null 如果不存在此类元素。
          // Element.scrollTop 属性可以获取或设置一个元素的内容垂直滚动的像素数。
          const scroll = document.documentElement.scrollTop || document.body.scrollTop;
          this.districtName.forEach((item) => {
            // parseInt 解析一个字符串并返回指定基数的十进制整数
            // ceil 向上取整
            // HTMLElement.offsetTop 返回当前元素相对于其 offsetParent 元素的顶部内边距的距离
            if (item.offsetTop - scroll < 100) {  // 标题距离顶部小于100时, 切换当前激活的目录
              this.activeLoadScroll(item.getAttribute("data-tab"));
              return;
            }
          });
        });
      }
      /**
       * 遍历目录节点, 高亮当前滚动到的节点
       * @param {string} sections 属性data-tab的值
       */
      activeLoadScroll(sections) {
        const categoryDomA = document.querySelectorAll(".directory #category ul li a");
        categoryDomA.forEach((item) => {
          // 添加激活类名active
          item.className = item.getAttribute("data-tab") == sections ? "active" : "";
        });
      }
    }

优化


  • 滚动监听添加防抖
            window.addEventListener("scroll", debounce((e) => {
                const scroll = document.documentElement.scrollTop || document.body.scrollTop;
                this.districtName.forEach((item) => {
                    if (item.offsetTop - scroll < 100) {
                        this.activeLoadScroll(item.getAttribute("data-tab"));
                        return;
                    }
                });
            }));
            function debounce(fun, delay = 500) {
                let timer = null;
                return function (...args) {
                    clearTimeout(timer);
                    timer = setTimeout(() => {
                        fun.apply(this, args)
                    }, delay)
                }
            }

效果

相关链接


参考文章1: https://blog.csdn.net/qq_43684588/article/details/125048254

参考文章2: https://juejin.cn/post/7037389069407485959

更多文章: <yujing.fit>

完整代码片段: https://gist.github.com/tiam-bloom/21165200d407e0443c0aaa3d66f27873


相关文章
|
Linux iOS开发 MacOS
CrossOver23中文永久免费版MacOS平台快速运行Windows软件
CrossOver是一款可以让Mac和Linux系统中正常运行Windows软件的应用程序。它不像虚拟机一样需要安装Windows系统之后才可以安装Windows的应用程序,这一方式给大多数用户带来了方便。通过CrossOver实现跨平台的文件复制粘贴,使Mac/Linux系统与Windows应用良好结合。CrossOver下载:http://t.csdn.cn/Ixdq6
4865 0
CrossOver23中文永久免费版MacOS平台快速运行Windows软件
|
NoSQL IDE 开发工具
使用Clion优雅的完全远程自动同步和远程调试c(下)
使用Clion优雅的完全远程自动同步和远程调试c(下)
使用Clion优雅的完全远程自动同步和远程调试c(下)
|
监控 Shell Linux
【Shell 命令集合 网络通讯 】Linux 共享文件和打印机 Samba 命令 使用指南
【Shell 命令集合 网络通讯 】Linux 共享文件和打印机 Samba 命令 使用指南
509 0
|
5月前
|
机器学习/深度学习 人工智能 自然语言处理
工程师的AGI落地指南:从基础概念到智能体开发的完整地图
本文系统讲解大型语言模型(LLM)核心技术与开发实践,涵盖基础概念、模型架构、训练方法、应用策略与伦理安全,适合AI开发者全面学习与参考。
466 0
|
10月前
|
小程序 数据管理
如何免费制作简历二维码,让你的简历脱颖而出
通过在简历上添加二维码,HR可以通过手机扫码,访问更多无法直接呈现在纸面上的内容——包括个人作品集、视频介绍、设计稿、详细项目经历甚至是动态的职业成长记录,让招聘者可以更全面的了解你
|
监控 JavaScript 前端开发
JavaScript与Nest.js:打造高性能的服务器端应用
Nest.js是Node.js的渐进式框架,融合OOP、FP和FRP,提供模块化、装饰器和依赖注入,助建高性能服务器应用。选择Nest.js的原因包括模块化设计、简洁的装饰器API和高性能基础(如Express或Fastify)。开始使用需安装Node.js和`@nestjs/cli`,创建项目、编写控制器。深入学习涉及模块化、服务的依赖注入及中间件。安全性优化涵盖HTTPS、CORS策略、限流和性能监控。
464 0
|
机器学习/深度学习 编解码 计算机视觉
【CVPR红外小目标检测】红外小目标检测中的非对称上下文调制(ACM)
【CVPR红外小目标检测】红外小目标检测中的非对称上下文调制(ACM)
751 1
|
数据采集 数据安全/隐私保护
高效网络采集实践:使用 Haskell 和 html-conduit 下载 www.baidu.com 视频完整教程
网络采集在当今信息时代中发挥着关键作用,用于从互联网上获取数据并进行分析。本文将介绍如何使用 Haskell 进行网络编程,从数据采集到图片分析,为你提供一个清晰的指南。我们将探讨如何使用爬虫代理来确保高效、可靠的数据获取,并使用 Haskell 的强大功能来分析和处理数据。
3047 1
|
定位技术 芯片
ARM架构与编程(基于I.MX6ULL): 串口UART编程(七)(上)
ARM架构与编程(基于I.MX6ULL): 串口UART编程(七)
645 1
ARM架构与编程(基于I.MX6ULL): 串口UART编程(七)(上)