前端实现文档内部导航效果

1. 前言

  • 实现文档内部导航效果,功能包括:
    • 滚动:用户滚动到相应的标题处,导航对应的标题元素加样式,并且导航框的内部滚动轴也会跟随滚动到合适的位置。
    • 点击:点击导航对应的标题元素或者置顶按钮,文档则跳到相应的位置并添加样式,并且导航框的内部滚动轴也会跟随滚动到合适的位置。
  • 代码由html、css和javascript组成;代码中的文本是由lorem(乱数假文)生成的
  • 每个代码块有注释解释,如果由不清楚的欢迎留言

2. html代码

  • 由于文档内容太长就没有放,生成文档结构命令为section[class="doc"]>(h2{navigator:$}+p*20>lorem5)*20
html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="./index.css" />
  </head>
  <body>
    <div class="content">
      <!-- 文档内容使用 section[class="doc"]>(h2{navigator:$}+p*20>lorem5)*20  生成-->
      
      <!-- 导航 -->
      <div class="doc-markup">
        <div class="doc-markup-header">
          <span>目录</span>
        </div>
        <div class="doc-markup-body"></div>
      </div>
    </div>
    <!-- 回到顶部 -->
    <div class="broadsideImg">
      <button class="gotop">
        <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" class="">
          <path
            fill-rule="evenodd"
            clip-rule="evenodd"
            d="M5.00806 3.0625C4.70158 3.0625 4.45312 3.31095 4.45312 3.61743C4.45312 3.92391 4.70158 4.17236 5.00806 4.17236H14.9968C15.3033 4.17236 15.5518 3.92391 15.5518 3.61743C15.5518 3.31095 15.3033 3.0625 14.9968 3.0625H5.00806ZM10.3948 5.9997C10.1781 5.78297 9.82676 5.78297 9.61005 5.9997L6.07849 9.53126C5.86176 9.74797 5.86176 10.0993 6.07849 10.316C6.2952 10.5328 6.64656 10.5328 6.86327 10.316L9.44751 7.73182V17.3809C9.44751 17.6873 9.69596 17.9358 10.0024 17.9358C10.3089 17.9358 10.5574 17.6873 10.5574 17.3809V7.73182L13.1416 10.316C13.3583 10.5328 13.7097 10.5328 13.9264 10.316C14.1431 10.0993 14.1431 9.74797 13.9264 9.53126L10.3948 5.9997Z"
            fill="#515767"
            stroke="#515767"
            stroke-width="0.5"
          ></path>
        </svg>
      </button>
    </div>
  </body>

  <script src="./index.js"></script>
</html>

3. js代码

1. 创建导航元素

js 复制代码
// 创建导航元素
function creatDocumentNavigation() {
  if (doms.length === 0) {
    docMarkup.style.display = "none"
    return false
  }
  docMarkup.style.display = "block"
  let ul = document.createElement("ul")
  ul.setAttribute("class", "doc-markup_ul")
  for (let i = 0; i < doms.length; i++) {
    // 创建li标签,设置内容
    const bookmarkContent = doms[i].textContent
    let li = document.createElement("li")
    li.textContent = bookmarkContent
    li.setAttribute("class", "doc-markup_li")
    li.setAttribute("title", bookmarkContent)
    //添加点击事件
    li.addEventListener("click", e => {
      e.preventDefault()
      document.documentElement.scrollTop = Math.floor(doms[i].offsetTop)
      isClickToJump = true
    })
    ul.appendChild(li)
  }
  docMarkupBody.appendChild(ul)
  // 监听文档滚动事件
  watchDocumentScroll()
  // 监听回到顶部事件
  watchBroadsideImgGotopClick()
}

2. 监听滚动事件

js 复制代码
function watchDocumentScroll() {
  const markupLi = document.querySelectorAll(".doc-markup-body .doc-markup_ul .doc-markup_li")
  let lastScrollTop = 0
  window.addEventListener("scroll", () => {
    let selectiIndex = null
    const currentScrollTop = document.documentElement.scrollTop
    // 这里是是对向上还是向下滚动做处理;大于零向下,反之向上
    if (currentScrollTop - lastScrollTop > 0 || isClickToJump) {
      isClickToJump = false
      doms.forEach((item, index) => {
        let reat = item.getBoundingClientRect()
        // 这里的判断是找到需要高亮的导航目录元素,当标题dom距离顶部小于50那么此标题dom就是高亮标题(reat.bottom指的是元素底部距离顶部的距离);向上滚动就请coder脑补一下子把
        if (reat.top < 50 && reat.bottom > 0) {
          //清除导航样式函数
          clearMarkupSelectClass()
          selectiIndex = index
        }
      })
    } else {
      doms.forEach((item, index) => {
        let reat = item.getBoundingClientRect()
        if (reat.top > 50 && reat.bottom < 150) {
          //清除导航样式函数
          clearMarkupSelectClass()
          index === 0 ? (selectiIndex = index) : (selectiIndex = index - 1)
        }
      })
    }
    if (selectiIndex !== null) {
      markupLi[selectiIndex].classList.add("markup-select")
      //大于4的原因是:我将导航高度设置了300,每个标题30,因此导航最高展示10个元素所以当下标大于4的即5个元素时开始触发导航目录的滚动轴
      if(selectiIndex >= 4){
        docMarkupBody.scrollTo({
          top: Math.floor(markupLi[selectiIndex-4].offsetTop) - 55,
          behavior: "smooth",
        })
      }else{
        docMarkupBody.scrollTo({
          top: 0,
          behavior: "smooth",
        })
      }
    }

    lastScrollTop = currentScrollTop
  })
}

3. 回到顶部按钮

js 复制代码
function watchBroadsideImgGotopClick() {
  const broadsideImgGotop = document.querySelector(".broadsideImg .gotop")
  broadsideImgGotop.addEventListener("click", (e) => {
    e.preventDefault()
    isClickToJump = true
    document.documentElement.scrollTop = 0
  })
}

4. 清除导航样式函数

js 复制代码
// 清除标记的样式
function clearMarkupSelectClass() {
  const markupLi = document.querySelectorAll(".doc-markup-body .doc-markup_ul .doc-markup_li")
  markupLi.forEach(item => {
    item.classList.remove("markup-select")
  })
}

4. 效果静态展示

5. 写在最后

  • 代码的简洁程度不够高,请大家不要介意;另外滚动函数可以加防抖来提交性能
  • 如果对代码有疑惑的地方欢迎留言。
相关推荐
FungLeo4 小时前
成为全栈·React 管理后台篇·总复盘:从“接口能调通”到“后台值得使用”
前端·react.js·前端框架·项目复盘·成为全栈
Csvn5 小时前
并发模式:让渲染学会排队、插队和让路
前端
可乐鸡翅yeah_6 小时前
新手梳理:M3U8 线上问题,哪些是前端锅,哪些是后端锅
前端·ios·音视频·实时音视频·m3u8·音视频在线播放
Flynt6 小时前
Linear 用 1000 个 PR 换掉 styled-components,我写了 200 个按钮,把这笔账复现了一遍
前端·css·preact
JavaGuide7 小时前
NVIDIA 又开源了!这次给 AI Agent 加上权限管控
前端·后端
excel7 小时前
prisma 如何处理数据库竞态
前端·数据库·后端
莪_幻尘8 小时前
Skill 体检:30 个 Skill 全凭感觉?体检器先自曝了 8 个“假 0 分
前端·人工智能·llm
风骏时光牛马8 小时前
AI模型综合能力评测:性能、指令遵循与多场景实测对比
前端
Frag0ut8 小时前
Chrome与Chromium内核浏览器在Windows 11上的新特性全景解析
前端·chrome·windows·web安全·chromium·gemini ai·playready drm
hiahiahia1239 小时前
实现完整 Tool Dispatcher
开发语言·前端