934 字
5 分鐘
移动端目录组件开发记录

功能概述#

移动端目录组件是博客的一个辅助功能,它可以在移动设备上显示文章的目录结构,帮助读者快速导航到指定章节。主要特点包括:

  1. 响应式设计:仅在移动端显示
  2. 浮动按钮:固定在右下角的快捷入口
  3. 面板式布局:从底部滑出的目录面板
  4. 实时更新:页面切换时自动更新目录内容

技术实现#

1. 组件结构#

使用 Svelte 开发,主要包含以下部分:

  • 浮动按钮
  • 目录面板
  • 背景遮罩
  • 目录内容区

基本结构示例:

{#if shouldShow}
  <!-- 移动端悬浮按钮 -->
  <button class="fixed bottom-20 right-4 z-50">
    <!-- 按钮内容 -->
  </button>

  {#if isOpen}
    <div role="dialog" class="fixed inset-0 z-[100]">
      <!-- 背景遮罩 -->
      <button class="absolute inset-0 bg-black/50"></button>

      <!-- 目录面板 -->
      <div class="absolute bottom-0 left-0 right-0">
        <!-- 面板内容 -->
      </div>
    </div>
  {/if}
{/if}

2. 状态管理#

主要的状态变量:

let isOpen = false;        // 面板开关状态
let isMobile = false;      // 移动端判断
let currentPath = '';      // 当前路径
let isHomePage = false;    // 首页判断
let mounted = false;       // 组件挂载状态
let currentHeadings = [];  // 当前目录内容
let shouldShow = false;    // 显示控制

3. 目录更新机制#

最初的实现中,我们尝试通过 props 传递目录数据:

export let headings = [];
$: if (mounted && headings) {
  currentHeadings = [...headings];
}

但这种方式在页面切换时无法及时更新。最终采用了直接从 DOM 获取标题的方案:

function updateContent() {
  if (!mounted) return;
  checkPath();
  
  // 获取当前页面的所有标题
  const allHeadings = Array.from(document.querySelectorAll('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]'))
    .map(heading => ({
      depth: parseInt(heading.tagName[1]),
      text: heading.textContent || '',
      slug: heading.id
    }));

  currentHeadings = allHeadings;
  shouldShow = isMobile && !isHomePage && currentHeadings.length > 0;
}

4. 页面切换处理#

为了确保在页面切换时正确更新目录,我们监听了 Astro 的视图转换事件:

function handlePageChange() {
  if (!mounted) return;
  isOpen = false;
  // 等待 DOM 更新完成
  setTimeout(() => {
    updateContent();
  }, 100);
}

onMount(() => {
  mounted = true;
  checkMobile();
  updateContent();

  window.addEventListener('resize', checkMobile);
  document.addEventListener('astro:page-load', handlePageChange);
  document.addEventListener('astro:after-swap', handlePageChange);
  
  return () => {
    window.removeEventListener('resize', checkMobile);
    document.removeEventListener('astro:page-load', handlePageChange);
    document.removeEventListener('astro:after-swap', handlePageChange);
  };
});

开发难点#

1. 目录更新问题#

最初遇到的主要问题是目录内容在页面切换后不会自动更新,需要手动刷新页面。这个问题的解决经历了几个阶段:

  1. 尝试使用 props 传递数据:无法及时响应页面变化
  2. 监听 Astro 的页面切换事件:时机不准确
  3. 最终采用直接从 DOM 获取标题的方案:解决了更新问题

2. 时机把控#

在页面切换时,需要确保在正确的时机更新目录内容:

  • 太早获取:DOM 还未更新,获取到的是旧内容
  • 太晚获取:用户体验不佳

解决方案是使用 setTimeout 配合事件监听:

setTimeout(() => {
  updateContent();
}, 100);

3. 性能优化#

主要优化点:

  • 避免不必要的 DOM 查询
  • 控制更新频率
  • 优化事件监听器的添加和移除

未来计划#

  1. 功能增强:

    • 添加目录项高亮功能
    • 优化动画效果
    • 支持更多的自定义选项
  2. 技术改进:

    • 改进移动端手势操作
    • 优化更新机制
    • 提供更多自定义选项
  3. 其他计划:

    • 添加滚动位置同步
    • 优化性能
    • 提供更多主题选择

总结#

移动端目录组件的开发过程让我们深入理解了:

  1. Astro 的视图转换机制
  2. Svelte 的响应式更新
  3. DOM 操作的时机控制
  4. 移动端交互设计

通过直接获取 DOM 内容的方式,我们最终解决了目录更新的问题,为移动端用户提供了更好的阅读体验。

参考资料#

  1. Svelte 官方文档
  2. Astro 文档 - View Transitions
  3. MDN - Intersection Observer API
移动端目录组件开发记录
https://zeox.pages.dev/posts/toc/
作者
Villode
發佈於
2024-02-22
許可協議
CC BY-NC-SA 4.0