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

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.hqwc.cn/news/527981.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java 中的 File 类常用方法介绍

Java 中的 File 类是 java.io 包的一部分&#xff0c;它提供了丰富的文件操作方法。File 类可以用来表示文件和目录路径名的抽象表示形式&#xff0c;即它可以用来获取文件或目录的属性&#xff0c;也可以用来创建、删除、重命名文件和目录。下面是一些常用的 File 类方法&…

JavaEE+springboot教学仪器设备管理系统o9b00-springmvc

本文旨在设计一款基于Java技术的教学仪器设备销售网站&#xff0c;以提高网站性能、功能完善、用户体验等方面的优势&#xff0c;解决现有教学仪器设备销售网站的问题&#xff0c;并为广大教育工作者和学生提供便捷的教学仪器设备销售渠道。本文首先介绍了Java技术的相关基础知…

深入理解Hive:探索不同的表类型及其应用场景

文章目录 1. 引言2. Hive表类型概览2.1 按照数据存储位置2.2 按照数据管理方式2.3 按照查询优化2.4 按照数据的临时性和持久性 3. 写在最后 1. 引言 在大数据时代&#xff0c;Hive作为一种数据仓库工具&#xff0c;为我们提供了强大的数据存储和查询能力。了解Hive的不同表类型…

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

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

Day30:安全开发-JS应用NodeJS指南原型链污染Express框架功能实现审计

目录 环境搭建-NodeJS-解析安装&库安装 功能实现-NodeJS-数据库&文件&执行 安全问题-NodeJS-注入&RCE&原型链 案例分析-NodeJS-CTF题目&源码审计 开发指南-NodeJS-安全SecGuide项目 思维导图 JS知识点&#xff1a; 功能&#xff1a;登录验证&…

【数理统计实验(四)】方差分析

&#x1f349;CSDN小墨&晓末:https://blog.csdn.net/jd1813346972 个人介绍: 研一&#xff5c;统计学&#xff5c;干货分享          擅长Python、Matlab、R等主流编程软件          累计十余项国家级比赛奖项&#xff0c;参与研究经费10w、40w级横向 文…

java之mybatis

准备工作 上面4步骤 XML映射文件 动态SQL

小程序 van-field label和输入框改成上下布局

在组件上面加个样式就行&#xff1a;custom-style"display:block;" <van-field label"备注说明" type"textarea" clearable title-width"100px" custom-style"display:block;" placeholder"请输入" /> …

Springboot 集成kafka 消费者实现ssl方式连接监听消息实现消费

证书准备&#xff1a;springboot集成kafka 消费者实现 如何配置是ssl方式连接的时候需要进行证书的转换。原始的证书是pem, 或者csr方式 和key方式的时候需要转换&#xff0c;因为kafka里面是jks 需要通过openssl进行转换。 证书处理&#xff1a; KeyStore 用于存储客户端的证…

macOS14.4安装FFmpeg及编译FFmpeg源码

下载二进制及源码包 二进制 使用brew安装ffmpeg : brew install ffmpeg 成功更新到ffmpeg6.1 下载FFmpeg源码

AHU 算法分析 实验四 动态规划

实验四&#xff1a;动态规划 实验目的 • 理解动态规划的基本思想&#xff0c;理解动态规划算法的两个基本要素最 优子结构性质和子问题的重叠性质。 • 熟练掌握典型的动态规划问题。 • 掌握动态规划思想分析问题的一般方法&#xff0c;对较简单的问题能正确 分析&#x…

云上攻防-云产品篇堡垒机场景JumpServer绿盟SASTeleport麒麟齐治

知识点 1、云产品-堡垒机-产品介绍&攻击事件 2、云产品-堡垒机-安全漏洞&影响产品 章节点&#xff1a; 云场景攻防&#xff1a;公有云&#xff0c;私有云&#xff0c;混合云&#xff0c;虚拟化集群&#xff0c;云桌面等 云厂商攻防&#xff1a;阿里云&#xff0c;腾讯…