Skip to content

TOC 实现

概述

本文档介绍了在目录(TOC)导航中渲染垂直线的正确方法。新的策略将线条渲染从锚点元素移至列表项元素,使用 border-left 替代 ::before 伪元素。

问题分析

旧方法(锚点上的 ::before)存在的问题

  • 高度计算问题:线条无法正确延伸覆盖整个列表项高度
  • 溢出问题:线条可能超出预期边界
  • 嵌套继承复杂:嵌套列表需要复杂的计算
  • 定位复杂:需用 calc() 进行绝对定位
  • 间隙覆盖难:难以保证线条连续无缝

解决方案:新方法(li 上的 border-left)

主要优势

  • 自动高度:边框会随内容高度自动增长
  • 无溢出:边框只在列表项内部
  • 自然继承:嵌套列表自然继承父级边框
  • 实现简单:无需复杂定位或计算
  • 无缝连续:父子项线条自然连接
  • 响应式友好:适用于动态内容和响应式布局

实现细节

核心 CSS 变更

// 新方法 - 应用于 li 元素
.toc-list-item {
    border-left: 2px solid #e5e7eb;
    padding-left: 1rem;
    margin-left: 0.5rem;
    margin-bottom: 0.25rem;
}

.toc-list-item a {
    color: #0a55a7;
    text-decoration: none;
    display: block;
    padding: 0.5rem 0;
    transition: all 0.2s ease;
    line-height: 1.4;
    // 无需 position: relative
    // 无需 ::before 伪元素
}

// 激活状态样式
.toc-list-item.active {
    border-left-color: #0a55a7;
    border-left-width: 3px;
}

.toc-list-item.active a {
    color: #0a55a7;
    font-weight: 600;
}

// 自然嵌套继承
.toc-list-item .toc-list {
    margin-top: 0.5rem;
}

.toc-list-item .toc-list .toc-list-item {
    margin-left: 0; // 继承父级边框
    border-left-color: #d1d5db; // 嵌套项更浅
}

// 顶级项去除边框
.toc-container > #TableOfContents > .toc-list > .toc-list-item {
    border-left: none;
    padding-left: 0;
    margin-left: 0;
}

具体文件修改

1. 修改 assets/scss/templates/_toc.scss

将第 30-61 行替换为:

#TableOfContents {
    > .toc-list {
        padding-left: 0 !important;

        > .toc-list-item {
            border-left: none; // 顶级项无边框
            padding-left: 0;
            margin-left: 0;
        }
    }

    .toc-list {
        padding-left: 0;

        .toc-list-item {
            border-left: 2px solid #e5e7eb;
            padding-left: 1rem;
            margin-left: 0.5rem;
            margin-bottom: 0.25rem;
        }

        .toc-list-item a {
            color: $primary-color;
            text-decoration: none;
            display: block;
            padding: 0.5rem 0;
            transition: all 0.2s ease;
            line-height: 1.4;
            // 移除所有 ::before 伪元素代码
        }

        .toc-list-item.active {
            border-left-color: $primary-color;
            border-left-width: 3px;
        }

        .toc-list-item.active a {
            color: $primary-color;
            font-weight: 600;
        }

        // 嵌套继承
        .toc-list-item .toc-list .toc-list-item {
            margin-left: 0;
            border-left-color: #d1d5db;
        }

        .is-active-link {
            font-weight: inherit;
        }
    }
}

2. 修改 assets/scss/layouts/book/_toc.scss

为书籍目录添加新方法:

#TableOfContents {
    padding-left: 0;
    overflow-y: auto;
    overflow-x: hidden;
    max-height: calc(100vh - 168px);
    height: auto;

    .toc-list-item {
        border-left: 2px solid #e5e7eb;
        padding-left: 1rem;
        margin-left: 0.5rem;
        margin-bottom: 0.25rem;
        display: block;
        word-break: break-word;
    }

    .toc-list-item a {
        display: block;
        line-height: 1.5;
        font-size: 0.9rem;
        color: $text-color-dark;
        text-decoration: none;
        padding: 0.5rem 0;
        transition: all 0.2s ease;
    }

    .toc-list-item a:hover {
        color: $primary-color;
    }

    .toc-list-item.active {
        border-left-color: $primary-color;
        border-left-width: 3px;
    }

    .toc-list-item.active a,
    .toc-list-item a.active {
        color: $primary-color;
        font-weight: 700;
    }

    // 顶级项去除边框
    > ul > .toc-list-item {
        border-left: none;
        padding-left: 0;
        margin-left: 0;
    }

    // 嵌套继承
    .toc-list-item .toc-list .toc-list-item {
        margin-left: 0;
        border-left-color: #d1d5db;
    }
}

3. 修改移动端 TOC 样式

在移动端 TOC 区块应用同样方法:

.mobile-toc-modal-body {
    #TableOfContents {
        ul {
            list-style: none;
            padding: 0;
            margin: 0;

            .toc-list-item {
                border-left: 2px solid #e5e7eb;
                padding-left: 1rem;
                margin-left: 0.5rem;
                margin-bottom: 0.25rem;
                border-bottom: 1px solid var(--card-border, #f0f0f0);

                &:last-child {
                    border-bottom: none;
                }

                a {
                    display: block;
                    padding: 1rem 0;
                    color: var(--text-color, #495057);
                    text-decoration: none;
                    transition: all 0.2s ease;
                    font-size: 0.9rem;
                    line-height: 1.5;

                    &:hover {
                        background: var(--card-border, #f8f9fa);
                        color: var(--accent-color, #0a55a7);
                    }

                    &.active {
                        color: var(--accent-color, #0a55a7);
                        font-weight: 500;
                    }
                }

                &.active {
                    border-left-color: var(--accent-color, #0a55a7);
                    border-left-width: 3px;
                }

                // 嵌套继承
                .toc-list .toc-list-item {
                    margin-left: 0;
                    border-left-color: #d1d5db;
                }
            }
        }
    }
}

验证点

新实现应满足:

  • ✅ 线条延伸至列表项全高
  • ✅ 无溢出
  • ✅ 嵌套列表自然继承边框
  • ✅ 激活状态正常
  • ✅ 响应式表现良好
  • ✅ CSS 简化,无复杂计算
  • ✅ 支持动态内容
  • ✅ 父子项线条无缝衔接

测试

原型文件展示:

  1. 基础结构 - 简单嵌套 TOC,含激活状态
  2. 深层嵌套 - 4 层及以上嵌套
  3. 长内容 - 多行标题自动换行
  4. 混合层级 - 多种嵌套模式
  5. 边界情况 - 空列表、单项、直接深层嵌套
  6. 交互行为 - 动态状态切换

优势总结

新方法消除了伪元素定位和计算的复杂性,带来:

  • 更可靠的线条渲染
  • 更佳的响应式表现
  • 更易维护
  • 自然嵌套继承
  • 各种场景下一致的视觉效果

此方法确保 TOC 导航中的垂直线条无论内容长度或嵌套深度都能保持健壮、易维护和一致的视觉表现。