jQuery 实现侧边栏文章目录:底部初始布局、滚动吸顶、标题高亮完整开发记录

2026-07-15 86 浏览 0 评论

在博客、文档、资讯类网站开发中,侧边栏文章目录是提升用户阅读体验的核心功能,能够帮助用户快速定位章节、实时查看阅读进度。本次开发基于原生 jQuery,基于已有的目录生成基础函数,完成 侧边栏底部初始布局、滚动吸顶固定、页面滚动标题高亮、平滑锚点跳转 全套功能开发,本文完整记录本次开发的需求、原始代码、迭代实现与核心逻辑。

一、初始开发需求与原始代码

1.1 核心功能需求

本次功能开发基于页面已有 tocBox 侧边栏目录容器,初始需求包含三个核心要点,精准匹配页面交互场景:

  • 初始布局规则 :侧边栏目录默认固定在侧边栏最底部,而非默认悬浮或顶部定位
  • 滚动吸顶规则 :页面滚动至目录可视区域时,目录自动固定在页面右侧顶部,保持常驻显示
  • 实时高亮规则 :页面滚动过程中,自动识别当前可视区域的 H1-H6 标题,对应侧边栏目录项高亮激活

1.2 项目原始基础代码

项目初始已存在一套 tocHandler 目录生成函数,可实现正文 H1~H6 标题自动抓取、唯一锚点生成、目录渲染、点击平滑跳转功能,原始完整代码如下,该代码已解决标题重复锚点冲突、默认锚点硬跳转问题:

function tocHandler() {
  const config = {
    contentBox: '.wtContent', // 正文容器,查找内部 h 标签
    tocList: '#toc-list', // 目录 ul 容器
    smoothSpeed: 500, // 平滑滚动速度 ms
  };

  let tocHtml = '';
  let idIndex = 0; // 自增 id,防止标题重复导致锚点冲突

  // 查找所有 h1~h6
  $(config.contentBox)
    .find('h1,h2,h3,h4,h5,h6')
    .each(function () {
      const $h = $(this);
      const tagName = $h.prop('tagName').toLowerCase(); // h1/h2...
      const text = $h.text().trim();
      // 生成唯一锚点 ID
      const anchorId = `toc-anchor-${idIndex++}`;
      // 给标题添加 id
      $h.attr('id', anchorId);
      // 拼接目录 li,按层级缩进
      tocHtml += `<li class="toc-${tagName}">
          <a href="#${anchorId}">${text}</a>
        </li>`;
    });

  if (tocHtml) {
    $('.tocBox').show();

    // 渲染目录
    $(config.tocList).html(tocHtml);

    // 点击目录平滑跳转
    $(config.tocList).on('click', 'a', function (e) {
      e.preventDefault(); // 阻止默认锚点瞬间跳转
      const targetId = $(this).attr('href');
      const $target = $(targetId);
      if ($target.length) {
        $('html, body').animate(
          {
            scrollTop: $target.offset().top - 50, // 顶部留 20px 偏移
          },
          config.smoothSpeed
        );
      }
    });
  }
}

1.3 原始代码能力与缺失

原始代码已完成 目录自动生成、层级渲染、唯一锚点防冲突、点击平滑跳转、无内容自动隐藏 基础能力,但完全缺失本次核心需求:无初始底部布局样式、无滚动吸顶逻辑、无页面滚动标题高亮功能,无法满足进阶交互体验。

二、完整迭代实现方案

基于原有基础函数,本次迭代补充 HTML 结构、CSS 布局样式、滚动监听、吸顶切换、标题映射、高亮激活、性能优化全套逻辑,完全保留原有基础功能,仅做功能叠加与优化。

2.1 配套 HTML 结构

沿用原有页面结构,包含正文内容容器与侧边目录容器,结构简洁无冗余,适配原有选择器规则:

<!-- 正文内容容器 -->
<div class="wtContent">
  <h1>一级标题示例</h1>
  <p>正文内容区域</p>
  <h2>二级标题示例</h2>
  <h3>三级标题示例</h3>
</div>

<!-- 侧边栏目录容器 -->
<div class="tocBox">
  <h4>文章目录</h4>
  <ul id="toc-list"></ul>
</div>

2.2 核心 CSS 样式实现

样式核心实现 默认底部定位、吸顶固定样式、标题层级缩进、高亮激活样式、滚动过渡动画 ,同时兼容多层级标题排版,优化目录滚动溢出效果:

/* 侧边栏父容器相对定位,为目录底部定位提供基准 */
.sidebar {
  width: 260px;
  position: relative;
  height: 100vh;
}

/* 目录默认样式:侧边栏底部定位、默认隐藏、溢出滚动 */
.tocBox {
  display: none;
  position: absolute;
  left: 0;
  bottom: 20px;
  width: 100%;
  padding: 16px;
  background: #fff;
  border: 1px solid #eee;
  border-radius: 8px;
  max-height: 70vh;
  overflow-y: auto;
  transition: all 0.2s ease;
}

/* 滚动吸顶激活样式:固定右侧顶部常驻显示 */
.tocBox.fixed-toc {
  position: fixed;
  top: 20px;
  bottom: auto;
  width: 240px;
  z-index: 99;
}

/* 目录列表基础样式 */
#toc-list {
  list-style: none;
  padding: 0;
  margin: 10px 0 0;
}
#toc-list li {
  margin: 6px 0;
}

/* 多层级标题缩进适配 H1-H6 */
#toc-list .toc-h1 { padding-left: 0; }
#toc-list .toc-h2 { padding-left: 12px; }
#toc-list .toc-h3 { padding-left: 24px; }
#toc-list .toc-h4 { padding-left: 36px; }
#toc-list .toc-h5 { padding-left: 48px; }
#toc-list .toc-h6 { padding-left: 60px; }

/* 目录链接默认样式 */
#toc-list li a {
  color: #666;
  text-decoration: none;
  font-size: 14px;
  line-height: 1.6;
  display: block;
}

/* 当前激活标题高亮样式 */
#toc-list li.active a {
  color: #1677ff;
  font-weight: 600;
  border-left: 3px solid #1677ff;
  padding-left: 8px;
}

2.3 完整迭代 JS 逻辑

在原有 tocHandler 函数基础上,新增 防抖性能优化、标题 DOM 映射初始化、滚动吸顶判断、实时高亮匹配、窗口重绘重置 逻辑,完全兼容原有功能,无破坏性修改:

$(function () {
  // 初始化目录
  tocHandler();

  // 全局变量定义
  const $tocBox = $('.tocBox');
  const $tocList = $('#toc-list');
  const $contentWrap = $('.wtContent');
  let headerOffset = 60; // 顶部导航偏移量
  let anchorList = []; // 标题与目录项映射数组

  // 滚动防抖函数:优化高频滚动性能
  function debounce(fn, delay = 80) {
    let timer = null;
    return function (...args) {
      clearTimeout(timer);
      timer = setTimeout(() => fn.apply(this, args), delay);
    }
  }

  // 初始化标题与目录项映射关系
  function initAnchorMap() {
    anchorList = [];
    $contentWrap.find('h1,h2,h3,h4,h5,h6').each(function (idx) {
      const $h = $(this);
      const $li = $tocList.find(`li:eq(${idx})`);
      anchorList.push({
        top: $h.offset().top - headerOffset - 10,
        $li: $li
      })
    });
  }

  // 滚动核心处理:吸顶切换 + 标题高亮
  function scrollHandle() {
    if ($tocBox.is(':hidden')) return;

    const scrollTop = $(window).scrollTop();
    const boxOriginTop = $tocBox.offset().top;

    // 目录吸顶逻辑:滚动超出原生位置则固定
    if (scrollTop >= boxOriginTop - headerOffset) {
      $tocBox.addClass('fixed-toc');
    } else {
      $tocBox.removeClass('fixed-toc');
    }

    // 标题高亮匹配逻辑
    let activeIdx = -1;
    for (let i = anchorList.length - 1; i >= 0; i--) {
      if (scrollTop >= anchorList[i].top) {
        activeIdx = i;
        break;
      }
    }
    // 重置所有高亮,激活当前匹配项
    $tocList.find('li').removeClass('active');
    if (activeIdx > -1) {
      anchorList[activeIdx].$li.addClass('active');
    }
  }

  // 绑定滚动、窗口缩放事件
  $(window).on('scroll', debounce(scrollHandle));
  $(window).on('resize', debounce(function () {
    initAnchorMap();
    scrollHandle();
  }));

  // 原有目录生成函数(仅新增映射初始化与首次渲染执行)
  function tocHandler() {
    const config = {
      contentBox: '.wtContent',
      tocList: '#toc-list',
      smoothSpeed: 500,
    };

    let tocHtml = '';
    let idIndex = 0;

    $(config.contentBox)
      .find('h1,h2,h3,h4,h5,h6')
      .each(function () {
        const $h = $(this);
        const tagName = $h.prop('tagName').toLowerCase();
        const text = $h.text().trim();
        const anchorId = `toc-anchor-${idIndex++}`;
        $h.attr('id', anchorId);
        tocHtml += `<li class="toc-${tagName}">
            <a href="#${anchorId}">${text}</a>
          </li>`;
      });

    if (tocHtml) {
      $('.tocBox').show();
      $(config.tocList).html(tocHtml);

      // 点击平滑跳转逻辑
      $(config.tocList).on('click', 'a', function (e) {
        e.preventDefault();
        const targetId = $(this).attr('href');
        const $target = $(targetId);
        if ($target.length) {
          $('html, body').animate(
            {
              scrollTop: $target.offset().top - headerOffset,
            },
            config.smoothSpeed
          );
        }
      });

      // 渲染完成初始化映射,首次加载执行滚动判断
      initAnchorMap();
      scrollHandle();
    }
  }
})

三、全功能核心逻辑复盘

3.1 初始底部布局逻辑

通过父容器 sidebar 设置相对定位,目录容器 tocBox 设置绝对定位、bottom 底部偏移,实现默认停靠在侧边栏最底部的初始状态,完全贴合需求初始布局规则。

3.2 滚动吸顶实现逻辑

通过获取目录原生初始位置的顶部偏移值,监听页面滚动高度:当页面滚动距离超过目录原生位置时,添加 fixed-toc 类,将目录切换为固定定位,常驻页面右侧顶部;页面回滚至初始位置上方时,移除固定类,目录自动回归侧边栏底部原始位置。

3.3 实时标题高亮逻辑

页面目录渲染完成后,自动遍历所有 H 标题与对应目录 DOM,建立坐标与 DOM 映射数组。页面滚动时,倒序遍历映射坐标,匹配当前视口内最顶部的标题,清除所有目录激活状态后,为当前匹配目录项添加 active 高亮样式,实现阅读进度实时同步。

3.4 性能优化逻辑

针对 scroll、resize 高频触发事件,加入防抖函数限制执行频率,避免 DOM 频繁计算、页面重绘导致的卡顿问题;同时窗口缩放时重新计算标题坐标,适配窗口尺寸变化后的布局偏移问题。

3.5 原有功能保留与优化

完整保留原始代码的所有能力:H1-H6 全层级目录自动生成、自增唯一 ID 解决标题重复锚点冲突、阻止默认锚点跳转实现平滑动画、无标题内容自动隐藏目录容器。同时统一全局滚动偏移量,让点击跳转、滚动定位的间距保持一致,交互更统一。

四、功能适配细节记录

  • 布局适配:依赖侧边栏父容器相对定位,保证目录初始底部定位不错乱
  • 层级适配:支持 H1~H6 六级标题差异化缩进展示,目录层级清晰
  • 交互适配:点击目录平滑跳转、滚动自动高亮、吸顶状态自动切换双向联动
  • 容错适配:无标题内容时自动隐藏目录,避免空白容器展示
  • 兼容适配:窗口大小变化自动重置坐标计算,适配响应式布局

五、开发总结

本次开发基于原有成熟的目录生成基础函数,采用 功能叠加、无侵入迭代 的开发思路,在不改动原有核心逻辑的前提下,补齐了侧边栏目录底部初始布局、滚动吸顶常驻、实时标题高亮三大核心能力。整套方案基于 jQuery 原生 API 实现,无第三方插件依赖,代码轻量化、逻辑清晰、性能稳定,完美适配博客、文档类页面的目录导航交互场景,实现了从基础目录渲染到沉浸式阅读导航的体验升级。


发布评论

发布评论前请先 登录
0 评论
点赞
收藏

评论列表 0

暂无评论