突破编程_前端_JS编程实例(目录导航)

news2024/9/29 17:37:59

1 开发目标

目录导航组件旨在提供一个滚动目录导航功能,使得用户可以方便地通过点击目录条目快速定位到对应的内容标题位置,同时也能够随着滚动条的移动动态显示当前位置在目录中的位置:

在这里插入图片描述

2 详细需求

2.1 标题提取与目录生成

  • 组件需要能够自动提取网页内容中的所有标题元素(如 h1, h2, h3 等)。
  • 提取的标题需要按照其在网页中的层级关系(如 h1 后面跟着的 h2 是其子章节)进行组织,形成一个目录容器。
  • 目录容器需以清晰、直观的方式展示给用户,允许用户通过点击目录条目进行导航。

2.2 滚动定位

  • 当用户在目录容器中点击某个目录条目时,网页的滚动条需要动态移动到对应的标题位置,使得该标题出现在页面的最上方。
  • 滚动过程应该平滑且快速,提升用户体验。

2.3 滚动条与目录条目交互

  • 当用户滚动网页的滚动条时,目录容器中的对应目录条目应该能够实时更新状态,以指示当前所在位置。
  • 当滚动条经过某个标题时,对应的目录条目应改变颜色(如高亮显示),以提醒用户当前的位置。

2.4 无滚动条情况处理

  • 如果网页内容较少,没有出现滚动条,那么点击目录条目时不应触发任何滚动动作。
  • 这种情况下,目录容器仍应正常显示,以供用户浏览网页内容的结构。

3 代码实现

首先创建一个 neat_directory.js 文件,该文件用于本组件的工具类、目录处理函数的代码构建。

(1)在具体的业务代码编写之前,先实现一个工具类以及一些工具方法,方便后面调用:

class CommonUtil {

    // 设置 DIV 中的文字为垂直居中
    static centerYTextInDiv(container) {
        container.style.display = 'flex';
        container.style.justifyContent = 'center';
        container.style.flexDirection = 'column';
    }

    // 判断 DIV 有无垂直滚动条
    static hasScrollbar(container) {
        var divStyle = window.getComputedStyle(container);
        var isOverflowing = container.scrollHeight > container.clientHeight;
        var isScrollbarVisible = isOverflowing &&
            (divStyle.overflow === 'scroll' || divStyle.overflow === 'auto' || divStyle.overflowY === 'scroll' || divStyle.overflowY === 'auto');

        return isScrollbarVisible;
    }
}

(2)接下来,开始定义目录节点类型,目录节点显示在目录区域:

class DirectoryNode {
    static LEVEL_OFFSET = 20;     // 每个级别的目录节点偏移像素
    static NODE_HEIGHT = '30px';     // 目录节点高度
    static NODE_NAME_FONTSIZE = '14px';     // 默认目录标题字符串的字体大小
    static NODE_NAME_COLOR = '#000';     // 默认目录标题字符串字体颜色
    static NODE_NAME_ACTIVE_COLOR = 'red';     // 默认目录标题在激活情况下字符串字体颜色

    constructor(container, para) {
        this.container = container;                 // 本目录节点的容器
        this.para = para;                           // 配置参数,包含页面内容的容器、标题容器以及标题等级等

        this.init();
    }

上面代码定义了 DirectoryNode 的一些默认属性与成员变量,并且创建构造函数,该函数接收调用者传入的 DIV 容器,并且调用 render 方法。
在 render 方法中,需要渲染当前目录节点,并且还要定义点击事件:

	render() {
        this.container.style.width = '100%';
        this.container.style.height = this.para.height ?? DirectoryNode.NODE_HEIGHT;
        this.container.style.fontSize = this.para.fontSize ?? DirectoryNode.NODE_NAME_FONTSIZE;
        this.container.style.color = this.para.color ?? DirectoryNode.NODE_NAME_COLOR;
        this.container.innerText = this.para.name;
        if (this.para.level > 1) {      // 设置目录节点偏移
            this.container.style.paddingLeft = ((this.para.level - 1) * DirectoryNode.LEVEL_OFFSET) + 'px';
        }
        this.container.style.cursor = 'pointer';

        // 点击事件
        let that = this;
        this.container.onclick = function () {
            that.para.onClick.call(that.para.onClickObj, that);
        }
    }

然后需要对目录节点的激活与非激活状态以及目录跳转逻辑做实现:

	// 目录节点激活并跳转对应目录位置
    activate() {
        this.container.style.color = DirectoryNode.NODE_NAME_ACTIVE_COLOR;
    }

    // 目录节点非激活
    deactivate() {
        this.container.style.color = this.para.color ?? DirectoryNode.NODE_NAME_COLOR;
    }

    // 目录跳转
    jump() {
        // 计算目标元素相对于父元素的位置  
        let targetElementRect = this.para.titleContainer.getBoundingClientRect();
        let parentRect = this.para.contentContainer.getBoundingClientRect();

        // 滚动到目标元素的顶部
        let offset = targetElementRect.top - parentRect.top + this.para.contentContainer.scrollTop;
        this.para.contentContainer.scrollTop = offset;
    }

    // 获取在页面内容的容器中,当前目录节点所对应的标题元素离顶部的距离
    getTopOffset() {
        let targetElementRect = this.para.titleContainer.getBoundingClientRect();
        let parentRect = this.para.contentContainer.getBoundingClientRect();
        return targetElementRect.top + parentRect.top;
    }
}

(3)在完成 DirectoryNode 的实现以后,开始创建目录类型 Directory :

class Directory {

    constructor(container, para) {
        this.container = container;                 // 传入的目录容器,用于渲染提取生成的目录
        this.para = para;                           // 配置参数,包含页面内容的容器
        this.nodes = [];                            // 目录节点集合
        this.jumpFlag=false;                        // 当前是否处于点击目录节点进行跳转的状态

        this.render();
    }

目录类型 Directory 的渲染函数 render 主要是获取页面内容中所有节点,遍历处理标题元素,然后创捷目录节点。此后,还需要定义页面内容的容器在滚动滚动轴时,触发目录变化的逻辑:

	render() {
        // 清空目录容器
        this.container.innerHTML = '';

        // 获取页面内容中所有节点,遍历处理标题元素
        let containerNodes = this.para.contentContainer.childNodes;
        containerNodes.forEach(element => {
            if (!element.tagName) {
                return;
            }
            let tagName = element.tagName.toUpperCase();
            if (2 == tagName.length) {
                let tagName1 = tagName.slice(0, 1);
                let tagName2 = tagName.slice(1, 2);
                if ('H' == tagName1 && !isNaN(tagName2)) {
                    let level = parseInt(tagName2);     // 标题等级
                    let directoryNodeContainer = document.createElement('div');
                    this.container.appendChild(directoryNodeContainer);
                    let nodePara = {
                        "name": element.innerText,
                        "level": level,
                        "titleContainer": element,
                        "contentContainer": this.para.contentContainer,
                        "onClick": this.jumpTo,
                        "onClickObj": this,
                    }
                    let node = new DirectoryNode(directoryNodeContainer, nodePara);
                    this.nodes.push(node);
                }
            }
        });

        // 页面内容的容器在滚动滚动轴时,触发目录变化
        let that = this;
        this.para.contentContainer.addEventListener('scroll', function () {
            // 如果网页内容较少,没有出现滚动条,那么页面内容的容器在滚动滚动轴时,不做任何触发
            // 如果当前是处于点击目录节点进行跳转的状态,则不做处理
            if (!CommonUtil.hasScrollbar(that.para.contentContainer) || that.jumpFlag) {
                return;
            }

            // 判断当前内容属于哪一个目录节点
            let activeNode=null;
            for (let index = 0; index < that.nodes.length; index++) {
                const node = that.nodes[index];
                if(node.getTopOffset()<0 && index+1<that.nodes.length && that.nodes[index+1].getTopOffset()>0){
                    activeNode = node;
                    break;
                }
            }
            if(null == activeNode && that.nodes.length>0){
                activeNode = that.nodes[0];
            }

            that.nodes.forEach(element => {
                element.deactivate();
            });
            activeNode.activate();
        });
    }

在完成渲染函数 render 的实现后,即要实现点击后目录跳转的功能,注意:如果网页内容较少,没有出现滚动条,那么点击目录条目时不应触发任何滚动动作:

	// 目录跳转
    jumpTo(node) {
        // 如果网页内容较少,没有出现滚动条,那么点击目录条目时不应触发任何滚动动作
        if (!CommonUtil.hasScrollbar(this.para.contentContainer)) {
            return;
        }

        this.jumpFlag=true; 
        this.nodes.forEach(element => {
            element.deactivate();
        });

        node.activate();
        node.jump();

        // 延迟一段时间
        let that = this;
        setTimeout(function() {  
            that.jumpFlag=false; 
        }, 100); 
    }
}

至此,整个目录导航功能的组件构建结束。

(4)完成目录导航功能的组件的代码编写后,可以创建 neat_directory.html 文件,调用该组件:

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>header tab</title>
    <style>
        html {
            height: 100%;
        }

        body {
            margin: 0;
            height: 100%;
        }
    </style>
</head>

<body>
    <div id="divMain" style="height: 100%;width: 100%;display: flex;">
        <div id="divDirectory" style="margin:10px;height: 500px;width: 300px;border: 1px solid #aaa;padding: 10px;">
        
        </div>
        <div id="divContent" style="margin:10px;height: 500px;width: 600px;border: 1px solid #aaa;padding: 10px;overflow-y: auto;">
            <h1>1 第一章</h1>
            <h2>1.1 第一章 第一节 </h2>
            <span>组件需要能够自动提取网页内容中的所有标题元素(如 h1, h2, h3 等)。</span>
            <span>提取的标题需要按照其在网页中的层级关系(如 h1 后面跟着的 h2 是其子章节)进行组织,形成一个目录容器。</span>
            <span>目录容器需以清晰、直观的方式展示给用户,允许用户通过点击目录条目进行导航。</span>
            <h3>1.1.1 第一章 第一节 第一段</h3>
            <span>组件需要能够自动提取网页内容中的所有标题元素(如 h1, h2, h3 等)。</span>
            <span>提取的标题需要按照其在网页中的层级关系(如 h1 后面跟着的 h2 是其子章节)进行组织,形成一个目录容器。</span>
            <span>目录容器需以清晰、直观的方式展示给用户,允许用户通过点击目录条目进行导航。</span>
            <h3>1.1.2 第一章 第一节 第二段</h3>
            <span>组件需要能够自动提取网页内容中的所有标题元素(如 h1, h2, h3 等)。</span>
            <span>提取的标题需要按照其在网页中的层级关系(如 h1 后面跟着的 h2 是其子章节)进行组织,形成一个目录容器。</span>
            <span>目录容器需以清晰、直观的方式展示给用户,允许用户通过点击目录条目进行导航。</span>
            <h1>2 第二章</h1>
            <h2>2.1 第二章 第一节 </h2>
            <span>组件需要能够自动提取网页内容中的所有标题元素(如 h1, h2, h3 等)。</span>
            <span>提取的标题需要按照其在网页中的层级关系(如 h1 后面跟着的 h2 是其子章节)进行组织,形成一个目录容器。</span>
            <span>目录容器需以清晰、直观的方式展示给用户,允许用户通过点击目录条目进行导航。</span>
            <h3>2.1.1 第二章 第一节 第一段</h3>
            <span>组件需要能够自动提取网页内容中的所有标题元素(如 h1, h2, h3 等)。</span>
            <span>提取的标题需要按照其在网页中的层级关系(如 h1 后面跟着的 h2 是其子章节)进行组织,形成一个目录容器。</span>
            <span>目录容器需以清晰、直观的方式展示给用户,允许用户通过点击目录条目进行导航。</span>
            <h3>2.1.2 第二章 第一节 第二段</h3>
            <span>组件需要能够自动提取网页内容中的所有标题元素(如 h1, h2, h3 等)。</span>
            <span>提取的标题需要按照其在网页中的层级关系(如 h1 后面跟着的 h2 是其子章节)进行组织,形成一个目录容器。</span>
            <span>目录容器需以清晰、直观的方式展示给用户,允许用户通过点击目录条目进行导航。</span>
        </div>
    </div>
</body>
<script src="./neat_directory.js"></script>
<script>
    let para = {
        "contentContainer":document.getElementById('divContent'),
    }
    let directory = new Directory(document.getElementById('divDirectory'),para);

</script>

</html>

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

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

相关文章

【PHP+代码审计】PHP基础——运算符

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java、PHP】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收…

数据分析-Pandas两种分组箱线图比较

数据分析-Pandas两种分组箱线图比较 数据分析和处理中&#xff0c;难免会遇到各种数据&#xff0c;那么数据呈现怎样的规律呢&#xff1f;不管金融数据&#xff0c;风控数据&#xff0c;营销数据等等&#xff0c;莫不如此。如何通过图示展示数据的规律&#xff1f; 数据表&am…

【prompt四】Domain Prompt Learning for Efficiently Adapting CLIP to Unseen Domains

motivation 领域泛化(DG)是一个复杂的迁移学习问题,旨在学习未知领域的可泛化模型。最近的基础模型(FMs)对许多分布变化都具有鲁棒性,因此,应该从本质上提高DG的性能。在这项工作中,我们研究了采用视觉语言基础模型CLIP来解决图像分类中的DG问题的通用方法。虽然ERM使用标…

【设计模式】(四)设计模式之工厂模式

1. 工厂模式介绍 工厂模式&#xff08;Factory Pattern&#xff09;是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式&#xff0c;它提供了一种创建对象的最佳方式。 工厂模式有三种实现方式&#xff1a; 简单工厂模式工厂方法模式抽象工厂模式 2. 工厂方…

部署LVS+Keepalived高可用群集(抢占模式,非抢占模式,延迟模式)

目录 一、LVSKeepalived高可用群集 1、实验环境 2、 主和备keepalived的配置 2.1 yum安装ipvsadm和keepalived工具 2.2 添加ip_vs模块并开启ipvsadm 2.3 修改keepalived的配置文件 2.4 调整proc响应参数&#xff0c;关闭linux内核的重定向参数响应 2.5 将主服务器的kee…

SSRF服务器请求伪造原理和pikachu靶场实验

★★免责声明★★ 文章中涉及的程序(方法)可能带有攻击性&#xff0c;仅供安全研究与学习之用&#xff0c;读者将信息做其他用途&#xff0c;由Ta承担全部法律及连带责任&#xff0c;文章作者不承担任何法律及连带责任。 1、SSRF简介 SSRF全称&#xff1a;Server-Side Request…

深入理解操作系统Operator System(2)

目录 操作系统对上的管理 系统调用接口 用户操作接口&#xff08;库函数&#xff09; 系统调用和库函数的概念 结构层次示意图 总结 为什么要有操作系统❓ 上次主要介绍了操作系统的"管理"和操作系统对下的管理。本篇主要是对上的管理。 操作系统对上的管理 …

【运维】本地部署Gemma模型(图文)

工具简介 我们需要使用到两个工具&#xff0c;一个是Ollama&#xff0c;另一个是open-webui。 Ollama Ollama 是一个开源的大语言平台&#xff0c;基于 Transformers 和 PyTorch 架构&#xff0c;基于问答交互方式&#xff0c;提供大语言模型常用的功能&#xff0c;如代码生…

软考高级:电子商务角色和类型概念和例题

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;大厂高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《Effective Java》独家解析》专栏作者。 热门文章推荐&am…

个人网站展示(静态)

大学期间做了一个个人博客网站&#xff0c;纯H5编码的网站&#xff0c;利用php搭建了一个留言模块。 有需要源码的同学&#xff0c;可以联系我~ 首页&#xff1a; IT杂记模块 文人墨客模块 劳有所获模块 生活日志模块 关于我 一个推崇全栈开发的前端开发人员 微信: itrzzh …

人工智能时代的产品管理:如何成为下一代引领者

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

字符串标记高亮脚本

源码 #!/bin/bash # usage: # echo hhh|mark str [font_color] [background_color] # font_color and background_color is optional, default is black&whiterp_str$1 f_color30 b_color47if [ "${f_color}a" "a" ]; thenf_color30 fiif [ "${…

网络安全: Kali Linux 进行 MSFvenom 程序利用

目录 一、实验 1.环境 2. Kali Linux 进行 MSFvenom 程序利用 3. 创建计划任务自动运行 MSFvenom 程序 二、问题 1.在线加密解密 2.MSF 运行失败 3.MobaXterm 连接Ubuntu 失败 一、实验 1.环境 &#xff08;1&#xff09;主机 表1 主机 系统版本IP备注Kali Linux20…

ElasticSearch之排序,fielddata和docvalue

写在前面 es搜索返回结果的排序默认是按照得分的高低来排的&#xff0c;本文来看下如何按照字段来排序&#xff0c;实现类似于MySQL的order by xxx的效果。 1&#xff1a;什么是fileddata和doc_value 参考ElasticSearch之零碎知识点 和一文带你彻底弄懂ES中的doc_values和fi…

直击Spring源码——高级容器

高级容器都实现了ApplicationContext功能&#xff0c;平常说的上下文就是高级容器 public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,MessageSource, ApplicationEventPublisher, ResourcePatternResolver { …

【笔记】原油阳谋论

文章目录 石油的属性能源属性各国石油替代 金融属性黄金石油美元 油价历史油价传导路径 石油供需格局与发展供需格局各国状况美国俄罗斯沙特 产油国困境运输 分析格局分析供需平衡分析价差分析价差概念基本面的跨区模型跨区模型下的价差逻辑 长中短三期分析长期视角——供应看投…

【CSS面试题】高度塌陷问题及解决

什么情况下产生 (when 父盒子没有定义高度&#xff0c;但是子元素有高度&#xff0c;希望用子盒子撑起父盒子的高度&#xff0c;但是子盒子添加了浮动属性之后&#xff0c;父盒子高度为0 <template><div class"father"><div class"son"&…

电脑打字突然变成繁体字如何修改

1. 右键电脑右下角的“中”字 2. 点击字符集&#xff0c;选中简体即可 有用的话记得给我点个赞啊~ 靴靴&#xff01;

阿里面试官:讲一讲熔断与降级的区别?

目录标题 1.什么是降级&#xff1f;1.1 降级的目标1.2 降级的策略1.3 降级级别1.4 什么时候降级&#xff1f;1.5 降级的机制1.6 降级的通知和沟通1.7 降级的监控和反馈1.8 降级预案和演练 2.什么是熔断&#xff1f;2.1 熔断的目标2.2 熔断的策略2.3 应用场景2.4 熔断的重要性 3…

活体检测(点头,摇头,张嘴等动态识别)

活体检测&#xff08;点头&#xff0c;摇头&#xff0c;张嘴等动态识别&#xff09; 某本书里有一句话&#xff0c;等我去读、去拍案。 田间的野老&#xff0c;等我去了解、去惊识。 山风与发&#xff0c;冷泉与舌&#xff0c; 流云与眼&#xff0c;松涛与耳&#xff0c; 他们等…