WordPress 无插件实现内容目录(TOC)完全指南

本文介绍了在WordPress中无插件实现文章内容目录的多种方法。无插件方案能优化性能、提供完全控制并避免插件冲突。具体包括纯CSS实现固定目录样式,以及PHP函数自动提取标题生成目录,并提供了详细的代码示例。

WordPress基础教程
阅读时间: 371 分钟
最后更新时间:2026年3月12日

在 WordPress 中为长篇文章添加内容目录(Table of Contents,TOC)可以显著改善用户体验和 SEO 表现。虽然有很多插件可以实现这个功能,但无插件方案可以提供更好的性能控制和代码简洁性。本文将详细介绍多种无插件实现内容目录的方法。

一、为什么需要无插件实现 TOC?

  1. 性能优化:减少插件依赖,提高页面加载速度
  2. 完全控制:自定义样式和功能
  3. 代码简洁:无需维护第三方插件更新
  4. 学习价值:深入理解 WordPress 开发原理
  5. 兼容性:避免与其他插件冲突

二、方法一:纯 CSS 实现简单目录

1. 基础 HTML 结构

首先确保文章使用正确的标题层级(H1-H6)。

2. CSS 实现固定目录

/* 在 style.css 中添加 */
.table-of-contents {
    position: fixed;
    right: 20px;
    top: 100px;
    width: 250px;
    max-height: 80vh;
    overflow-y: auto;
    background: #f8f9fa;
    border: 1px solid #dee2e6;
    border-radius: 8px;
    padding: 20px;
    z-index: 1000;
    display: none; /* 默认隐藏,通过JS控制显示 */
}

.toc-visible {
    display: block;
}

.table-of-contents h3 {
    margin-top: 0;
    font-size: 18px;
    color: #333;
    border-bottom: 2px solid #0073aa;
    padding-bottom: 8px;
}

.table-of-contents ul {
    margin: 0;
    padding-left: 20px;
    list-style-type: none;
}

.table-of-contents li {
    margin-bottom: 8px;
    line-height: 1.4;
}

.table-of-contents a {
    color: #555;
    text-decoration: none;
    font-size: 14px;
    transition: color 0.3s;
}

.table-of-contents a:hover {
    color: #0073aa;
    text-decoration: underline;
}

/* 层级缩进 */
.toc-level-2 { padding-left: 0; }
.toc-level-3 { padding-left: 15px; }
.toc-level-4 { padding-left: 30px; }
.toc-level-5 { padding-left: 45px; }
.toc-level-6 { padding-left: 60px; }

/* 当前活跃章节高亮 */
.table-of-contents a.active {
    color: #0073aa;
    font-weight: bold;
    border-left: 3px solid #0073aa;
    padding-left: 5px;
}

三、方法二:PHP 函数自动生成目录

1. 核心函数 – 自动提取标题

/**
 * 自动生成文章目录
 * 
 * @param string $content 文章内容
 * @return string 添加目录后的内容
 */
function auto_generate_toc($content) {
    // 只在单篇文章页面显示
    if (!is_single()) {
        return $content;
    }
    
    // 获取文章内容
    global $post;
    
    // 匹配所有标题(h2-h4)
    $headings = array();
    $pattern = '/<h([2-4])[^>]*>(.*?)<\/h[2-4]>/i';
    
    if (preg_match_all($pattern, $content, $matches, PREG_SET_ORDER)) {
        foreach ($matches as $match) {
            $level = intval($match[1]);
            $text = strip_tags($match[2]);
            $anchor = sanitize_title($text);
            
            // 为标题添加ID锚点
            $content = preg_replace(
                '/<h' . $level . '[^>]*>' . preg_quote($match[2], '/') . '<\/h' . $level . '>/',
                '<h' . $level . ' id="' . $anchor . '">' . $match[2] . '</h' . $level . '>',
                $content,
                1
            );
            
            $headings[] = array(
                'level' => $level,
                'text' => $text,
                'anchor' => $anchor
            );
        }
    }
    
    // 如果标题数量少于3个,不生成目录
    if (count($headings) < 3) {
        return $content;
    }
    
    // 生成目录HTML
    $toc = '<div class="table-of-contents" id="article-toc">';
    $toc .= '<h3>📑 文章目录</h3>';
    $toc .= '<ul class="toc-list">';
    
    foreach ($headings as $heading) {
        $indent_class = 'toc-level-' . $heading['level'];
        $toc .= '<li class="' . $indent_class . '">';
        $toc .= '<a href="#' . $heading['anchor'] . '" data-anchor="' . $heading['anchor'] . '">';
        $toc .= $heading['text'];
        $toc .= '</a></li>';
    }
    
    $toc .= '</ul>';
    $toc .= '</div>';
    
    // 在文章开头添加目录
    $content = $toc . $content;
    
    return $content;
}
add_filter('the_content', 'auto_generate_toc');

2. 带开关控制的增强版

/**
 * 增强版自动生成目录,支持开关控制
 */
function enhanced_auto_toc($content) {
    // 检查是否在文章页面
    if (!is_single() && !is_page()) {
        return $content;
    }
    
    global $post;
    
    // 通过自定义字段控制是否显示目录
    $show_toc = get_post_meta($post->ID, '_show_table_of_contents', true);
    
    // 如果没有设置,默认在文章字数大于1500时显示
    if ($show_toc === '' && str_word_count(wp_strip_all_tags($content)) < 1500) {
        return $content;
    }
    
    // 明确设置不显示
    if ($show_toc === 'no') {
        return $content;
    }
    
    // 匹配h2-h4标题
    $headings = array();
    $pattern = '/<h([2-4])(.*?)>(.*?)<\/h[2-4]>/i';
    
    if (!preg_match_all($pattern, $content, $matches, PREG_SET_ORDER)) {
        return $content;
    }
    
    // 如果标题少于3个,不生成目录
    if (count($matches) < 3) {
        return $content;
    }
    
    // 处理标题
    foreach ($matches as $match) {
        $level = intval($match[1]);
        $attrs = $match[2];
        $text = strip_tags($match[3]);
        
        // 检查是否已有ID
        if (preg_match('/id="([^"]*)"/i', $attrs, $id_match)) {
            $anchor = $id_match[1];
        } else {
            $anchor = sanitize_title($text);
            $content = str_replace($match[0], 
                '<h' . $level . ' id="' . $anchor . '"' . $attrs . '>' . $match[3] . '</h' . $level . '>', 
                $content);
        }
        
        $headings[] = array(
            'level' => $level,
            'text' => $text,
            'anchor' => $anchor
        );
    }
    
    // 生成目录
    $toc_html = generate_toc_html($headings);
    
    // 插入目录
    $content = insert_toc_into_content($content, $toc_html);
    
    return $content;
}
add_filter('the_content', 'enhanced_auto_toc', 9);

/**
 * 生成目录HTML
 */
function generate_toc_html($headings) {
    $toc = '<div class="article-toc-wrapper">';
    $toc .= '<div class="toc-header">';
    $toc .= '<h3 class="toc-title">📋 本文目录</h3>';
    $toc .= '<button class="toc-toggle" aria-label="折叠/展开目录">▼</button>';
    $toc .= '</div>';
    $toc .= '<div class="toc-content">';
    $toc .= '<ul class="toc-list">';
    
    $current_level = 2;
    
    foreach ($headings as $heading) {
        $level_class = 'toc-item-level-' . $heading['level'];
        
        $toc .= '<li class="' . $level_class . '">';
        $toc .= '<a href="#' . esc_attr($heading['anchor']) . '" class="toc-link">';
        $toc .= '<span class="toc-bullet"></span>';
        $toc .= '<span class="toc-text">' . esc_html($heading['text']) . '</span>';
        $toc .= '</a>';
        $toc .= '</li>';
    }
    
    $toc .= '</ul>';
    $toc .= '</div>';
    $toc .= '</div>';
    
    return $toc;
}

/**
 * 将目录插入到文章中
 */
function insert_toc_into_content($content, $toc_html) {
    // 尝试在第一个标题前插入
    $pattern = '/<h[2-4][^>]*>/';
    
    if (preg_match($pattern, $content, $matches, PREG_OFFSET_CAPTURE)) {
        $pos = $matches[0][1];
        $content = substr_replace($content, $toc_html, $pos, 0);
    } else {
        // 如果没有找到标题,在文章开头插入
        $content = $toc_html . $content;
    }
    
    return $content;
}

四、方法三:Shortcode 手动控制目录

1. 注册 Shortcode

/**
 * 注册目录 Shortcode
 */
function toc_shortcode($atts, $content = null) {
    global $post;
    
    // Shortcode 属性
    $atts = shortcode_atts(array(
        'depth' => 3,           // 标题深度(2-6)
        'title' => '文章目录',   // 目录标题
        'min' => 3,             // 最少标题数
        'collapsible' => true,  // 是否可折叠
        'position' => 'left',   // 位置:left, right, top
    ), $atts);
    
    // 获取文章内容
    $post_content = $post->post_content;
    
    // 提取标题
    $headings = extract_headings_from_content($post_content, $atts['depth']);
    
    // 如果标题数量不足
    if (count($headings) < $atts['min']) {
        return '<div class="toc-notice">目录不可用:文章内容结构不够清晰</div>';
    }
    
    // 生成目录HTML
    $toc_html = generate_toc_from_headings($headings, $atts);
    
    return $toc_html;
}
add_shortcode('toc', 'toc_shortcode');

/**
 * 从内容中提取标题
 */
function extract_headings_from_content($content, $max_depth = 3) {
    $headings = array();
    $pattern = '/<h([2-' . $max_depth . '])(.*?)>(.*?)<\/h[2-' . $max_depth . ']>/i';
    
    if (preg_match_all($pattern, $content, $matches, PREG_SET_ORDER)) {
        foreach ($matches as $match) {
            $level = intval($match[1]);
            $attrs = $match[2];
            $text = strip_tags($match[3]);
            
            // 提取或生成锚点ID
            if (preg_match('/id="([^"]*)"/i', $attrs, $id_match)) {
                $anchor = $id_match[1];
            } else {
                $anchor = sanitize_title($text);
            }
            
            $headings[] = array(
                'level' => $level,
                'text' => $text,
                'anchor' => $anchor
            );
        }
    }
    
    return $headings;
}

/**
 * 从标题生成目录
 */
function generate_toc_from_headings($headings, $atts) {
    $wrapper_class = 'toc-shortcode';
    if ($atts['collapsible']) {
        $wrapper_class .= ' toc-collapsible';
    }
    $wrapper_class .= ' toc-position-' . $atts['position'];
    
    $html = '<div class="' . esc_attr($wrapper_class) . '" data-min-headings="' . esc_attr($atts['min']) . '">';
    
    // 标题栏
    $html .= '<div class="toc-header">';
    $html .= '<h4 class="toc-title">' . esc_html($atts['title']) . '</h4>';
    if ($atts['collapsible']) {
        $html .= '<button class="toc-toggle-btn" aria-label="' . ($atts['collapsible'] ? '展开' : '折叠') . '目录">';
        $html .= '<span class="toggle-icon">+</span>';
        $html .= '</button>';
    }
    $html .= '</div>';
    
    // 目录内容
    $html .= '<div class="toc-body" style="' . ($atts['collapsible'] ? 'display:block;' : '') . '">';
    $html .= '<ul class="toc-list">';
    
    foreach ($headings as $heading) {
        $level_class = 'toc-level-' . $heading['level'];
        $html .= '<li class="toc-item ' . $level_class . '">';
        $html .= '<a href="#' . esc_attr($heading['anchor']) . '" class="toc-link">';
        $html .= '<span class="toc-bullet">•</span>';
        $html .= '<span class="toc-text">' . esc_html($heading['text']) . '</span>';
        $html .= '</a>';
        $html .= '</li>';
    }
    
    $html .= '</ul>';
    $html .= '</div>';
    $html .= '</div>';
    
    return $html;
}

2. Shortcode 使用方法

在文章编辑器中插入:


  // 使用默认设置

  // 自定义设置

  // 右侧固定,不可折叠

五、方法四:Gutenberg 区块实现

1. 创建目录区块

/**
 * 注册 Gutenberg 目录区块
 */
function register_toc_block() {
    // 注册区块脚本
    wp_register_script(
        'toc-block',
        get_template_directory_uri() . '/js/toc-block.js',
        array('wp-blocks', 'wp-element', 'wp-editor', 'wp-components', 'wp-i18n'),
        filemtime(get_template_directory() . '/js/toc-block.js')
    );
    
    // 注册区块样式
    wp_register_style(
        'toc-block-style',
        get_template_directory_uri() . '/css/toc-block.css',
        array(),
        filemtime(get_template_directory() . '/css/toc-block.css')
    );
    
    // 注册区块
    register_block_type('my-theme/table-of-contents', array(
        'editor_script' => 'toc-block',
        'editor_style'  => 'toc-block-style',
        'style'         => 'toc-block-style',
        'render_callback' => 'render_toc_block',
        'attributes' => array(
            'depth' => array(
                'type' => 'number',
                'default' => 3,
            ),
            'title' => array(
                'type' => 'string',
                'default' => '文章目录',
            ),
            'collapsible' => array(
                'type' => 'boolean',
                'default' => true,
            ),
        ),
    ));
}
add_action('init', 'register_toc_block');

/**
 * 渲染目录区块
 */
function render_toc_block($attributes) {
    global $post;
    
    $depth = isset($attributes['depth']) ? $attributes['depth'] : 3;
    $title = isset($attributes['title']) ? $attributes['title'] : '文章目录';
    $collapsible = isset($attributes['collapsible']) ? $attributes['collapsible'] : true;
    
    // 提取文章标题
    $headings = extract_headings_from_post($post, $depth);
    
    if (empty($headings)) {
        return '<div class="toc-empty">无可用目录</div>';
    }
    
    // 生成HTML
    $output = '<div class="wp-block-my-theme-table-of-contents' . ($collapsible ? ' toc-collapsible' : '') . '">';
    $output .= '<div class="toc-header">';
    $output .= '<h3>' . esc_html($title) . '</h3>';
    if ($collapsible) {
        $output .= '<button class="toc-toggle">▼</button>';
    }
    $output .= '</div>';
    $output .= '<div class="toc-content">';
    $output .= '<ul class="toc-list">';
    
    foreach ($headings as $heading) {
        $output .= '<li class="toc-level-' . $heading['level'] . '">';
        $output .= '<a href="#' . esc_attr($heading['anchor']) . '">';
        $output .= esc_html($heading['text']);
        $output .= '</a></li>';
    }
    
    $output .= '</ul></div></div>';
    
    return $output;
}

2. 区块 JavaScript 文件

// js/toc-block.js
const { registerBlockType } = wp.blocks;
const { TextControl, RangeControl, ToggleControl, PanelBody, InspectorControls } = wp.editor;
const { __ } = wp.i18n;

registerBlockType('my-theme/table-of-contents', {
    title: __('文章目录', 'my-theme'),
    icon: 'list-view',
    category: 'layout',
    
    attributes: {
        depth: {
            type: 'number',
            default: 3,
        },
        title: {
            type: 'string',
            default: __('文章目录', 'my-theme'),
        },
        collapsible: {
            type: 'boolean',
            default: true,
        },
    },
    
    edit: function(props) {
        const { attributes, setAttributes } = props;
        
        return [
            wp.element.createElement(
                InspectorControls,
                { key: 'inspector' },
                wp.element.createElement(
                    PanelBody,
                    { title: __('目录设置', 'my-theme') },
                    wp.element.createElement(TextControl, {
                        label: __('标题', 'my-theme'),
                        value: attributes.title,
                        onChange: (value) => setAttributes({ title: value }),
                    }),
                    wp.element.createElement(RangeControl, {
                        label: __('标题深度', 'my-theme'),
                        value: attributes.depth,
                        onChange: (value) => setAttributes({ depth: value }),
                        min: 2,
                        max: 6,
                    }),
                    wp.element.createElement(ToggleControl, {
                        label: __('可折叠', 'my-theme'),
                        checked: attributes.collapsible,
                        onChange: (value) => setAttributes({ collapsible: value }),
                    })
                )
            ),
            
            wp.element.createElement(
                'div',
                { className: props.className },
                wp.element.createElement(
                    'div',
                    { className: 'toc-preview' },
                    wp.element.createElement(
                        'h3',
                        null,
                        attributes.title
                    ),
                    wp.element.createElement(
                        'div',
                        null,
                        __('目录将在前台自动生成', 'my-theme')
                    )
                )
            )
        ];
    },
    
    save: function() {
        return null; // 动态渲染
    }
});

六、完整的 CSS 样式

/* 目录基础样式 */
.table-of-contents,
.article-toc-wrapper,
.toc-shortcode,
.wp-block-my-theme-table-of-contents {
    background: #ffffff;
    border: 1px solid #e0e0e0;
    border-radius: 8px;
    padding: 20px;
    margin: 20px 0;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    position: relative;
}

/* 目录标题 */
.toc-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 15px;
    padding-bottom: 10px;
    border-bottom: 2px solid #0073aa;
}

.toc-title {
    margin: 0;
    font-size: 18px;
    font-weight: 600;
    color: #2c3e50;
}

/* 折叠按钮 */
.toc-toggle,
.toc-toggle-btn {
    background: none;
    border: 1px solid #ddd;
    border-radius: 4px;
    padding: 4px 8px;
    cursor: pointer;
    font-size: 12px;
    transition: all 0.3s;
}

.toc-toggle:hover,
.toc-toggle-btn:hover {
    background: #f5f5f5;
    border-color: #0073aa;
}

/* 目录列表 */
.toc-list {
    list-style: none;
    margin: 0;
    padding: 0;
    max-height: 400px;
    overflow-y: auto;
}

.toc-item {
    margin: 8px 0;
    line-height: 1.4;
}

/* 层级缩进 */
.toc-item-level-2 { padding-left: 0; }
.toc-item-level-3 { padding-left: 20px; }
.toc-item-level-4 { padding-left: 40px; }
.toc-item-level-5 { padding-left: 60px; }
.toc-item-level-6 { padding-left: 80px; }

/* 链接样式 */
.toc-link {
    display: flex;
    align-items: flex-start;
    color: #555;
    text-decoration: none;
    padding: 6px 0;
    transition: all 0.2s;
    border-radius: 4px;
    padding-left: 8px;
}

.toc-link:hover {
    color: #0073aa;
    background: #f0f7ff;
    text-decoration: none;
    padding-left: 12px;
}

.toc-link.active {
    color: #0073aa;
    font-weight: 600;
    border-left: 3px solid #0073aa;
    background: #f0f7ff;
}

/* 项目符号 */
.toc-bullet {
    display: inline-block;
    width: 6px;
    height: 6px;
    background: #0073aa;
    border-radius: 50%;
    margin-right: 10px;
    margin-top: 7px;
    flex-shrink: 0;
}

.toc-text {
    flex-grow: 1;
}

/* 响应式设计 */
@media (max-width: 768px) {
    .table-of-contents {
        position: static;
        width: 100%;
        max-height: none;
    }
    
    .toc-list {
        max-height: 300px;
    }
    
    .toc-item-level-4 { padding-left: 30px; }
    .toc-item-level-5 { padding-left: 45px; }
    .toc-item-level-6 { padding-left: 60px; }
}

/* 浮动目录样式 */
.toc-floating {
    position: fixed;
    right: 20px;
    top: 120px;
    width: 280px;
    max-height: calc(100vh - 200px);
    z-index: 999;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}

/* 动画效果 */
.toc-content {
    transition: max-height 0.3s ease-out;
    overflow: hidden;
}

.toc-collapsed .toc-content {
    max-height: 0;
    padding-top: 0;
    padding-bottom: 0;
    margin: 0;
}

七、JavaScript 交互功能

// js/toc-interactive.js
jQuery(document).ready(function($) {
    'use strict';
    
    // 平滑滚动和活跃状态
    function initTableOfContents() {
        var $toc = $('.table-of-contents, .article-toc-wrapper, .toc-shortcode');
        
        if (!$toc.length) return;
        
        // 平滑滚动
        $toc.on('click', 'a[href^="#"]', function(e) {
            e.preventDefault();
            
            var target = $(this).attr('href');
            if ($(target).length) {
                $('html, body').animate({
                    scrollTop: $(target).offset().top - 100
                }, 500);
                
                // 更新URL hash
                if (history.pushState) {
                    history.pushState(null, null, target);
                } else {
                    window.location.hash = target;
                }
            }
        });
        
        // 折叠/展开功能
        $toc.find('.toc-toggle, .toc-toggle-btn').on('click', function() {
            var $tocBody = $(this).closest('.toc-header').next('.toc-content');
            var $icon = $(this).find('.toggle-icon');
            
            if ($tocBody.is(':visible')) {
                $tocBody.slideUp(300);
                $icon.text('+');
                $(this).attr('aria-label', '展开目录');
            } else {
                $tocBody.slideDown(300);
                $icon.text('-');
                $(this).attr('aria-label', '折叠目录');
            }
        });
        
        // 浮动目录
        if ($toc.hasClass('toc-floating')) {
            var $window = $(window);
            var tocTop = $toc.offset().top;
            var tocHeight = $toc.outerHeight();
            
            $window.on('scroll', function() {
                var scrollTop = $window.scrollTop();
                var windowHeight = $window.height();
                
                if (scrollTop > tocTop) {
                    if (scrollTop + windowHeight > tocTop + tocHeight + 100) {
                        $toc.addClass('toc-floating-fixed');
                    } else {
                        $toc.removeClass('toc-floating-fixed');
                    }
                } else {
                    $toc.removeClass('toc-floating-fixed');
                }
            });
        }
        
        // 活跃章节高亮
        function highlightActiveSection() {
            var scrollTop = $(window).scrollTop();
            var headings = $('h2[id], h3[id], h4[id]');
            var currentActive = null;
            
            headings.each(function() {
                var $heading = $(this);
                var offsetTop = $heading.offset().top;
                
                if (offsetTop - 150 <= scrollTop) {
                    currentActive = '#' + $heading.attr('id');
                }
            });
            
            // 移除所有活跃状态
            $toc.find('a').removeClass('active');
            
            // 添加当前活跃状态
            if (currentActive) {
                $toc.find('a[href="' + currentActive + '"]').addClass('active');
            }
        }
        
        // 节流滚动事件
        var scrollTimer = null;
        $(window).on('scroll', function() {
            if (scrollTimer) clearTimeout(scrollTimer);
            scrollTimer = setTimeout(highlightActiveSection, 100);
        });
        
        // 初始高亮
        setTimeout(highlightActiveSection, 100);
        
        // 目录可见性切换
        var $tocToggleBtn = $('<button class="toc-visibility-toggle" aria-label="显示/隐藏目录">📋</button>');
        $('body').append($tocToggleBtn);
        
        $tocToggleBtn.on('click', function() {
            $toc.toggleClass('toc-visible');
            var isVisible = $toc.hasClass('toc-visible');
            $(this).attr('aria-label', isVisible ? '隐藏目录' : '显示目录');
            $(this).text(isVisible ? '✕' : '📋');
        });
    }
    
    // 初始化
    initTableOfContents();
    
    // 延迟加载图片后重新初始化
    $(document).on('lazybeforeunveil', function() {
        setTimeout(initTableOfContents, 100);
    });
});

八、高级功能:可配置的目录系统

/**
 * 可配置的目录系统
 */
class TableOfContents {
    
    private static $instance = null;
    private $options = array();
    
    public static function get_instance() {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    private function __construct() {
        $this->options = get_option('toc_settings', array(
            'auto_insert' => true,
            'min_headings' => 3,
            'depth' => 4,
            'position' => 'top',
            'collapsible' => true,
            'smooth_scroll' => true,
            'highlight_active' => true,
        ));
        
        add_action('wp_enqueue_scripts', array($this, 'enqueue_assets'));
        add_action('admin_menu', array($this, 'add_admin_menu'));
        add_action('admin_init', array($this, 'register_settings'));
        
        if ($this->options['auto_insert']) {
            add_filter('the_content', array($this, 'auto_insert_toc'), 9);
        }
        
        add_shortcode('toc', array($this, 'toc_shortcode'));
    }
    
    /**
     * 自动插入目录
     */
    public function auto_insert_toc($content) {
        if (!is_single() && !is_page()) {
            return $content;
        }
        
        // 检查是否通过短代码已插入
        if (has_shortcode($content, 'toc')) {
            return $content;
        }
        
        // 检查文章是否排除
        global $post;
        $exclude = get_post_meta($post->ID, '_exclude_toc', true);
        if ($exclude) {
            return $content;
        }
        
        $headings = $this->extract_headings($content);
        
        if (count($headings) < $this->options['min_headings']) {
            return $content;
        }
        
        $toc_html = $this->generate_toc_html($headings);
        
        return $this->insert_toc($content, $toc_html);
    }
    
    /**
     * 提取标题
     */
    private function extract_headings($content) {
        $headings = array();
        $max_depth = min(6, max(2, $this->options['depth']));
        $pattern = '/<h([2-' . $max_depth . '])(.*?)>(.*?)<\/h[2-' . $max_depth . ']>/i';
        
        if (preg_match_all($pattern, $content, $matches, PREG_SET_ORDER)) {
            foreach ($matches as $match) {
                $level = intval($match[1]);
                $attrs = $match[2];
                $text = strip_tags($match[3]);
                
                // 跳过特定类
                if (strpos($attrs, 'no-toc') !== false) {
                    continue;
                }
                
                // 获取或生成锚点
                if (preg_match('/id="([^"]*)"/i', $attrs, $id_match)) {
                    $anchor = $id_match[1];
                } else {
                    $anchor = sanitize_title($text);
                    $content = str_replace($match[0], 
                        '<h' . $level . ' id="' . $anchor . '"' . $attrs . '>' . $match[3] . '</h' . $level . '>', 
                        $content
                    );
                }
                
                $headings[] = array(
                    'level' => $level,
                    'text' => $text,
                    'anchor' => $anchor
                );
            }
        }
        
        return $headings;
    }
    
    /**
     * 生成目录HTML
     */
    private function generate_toc_html($headings) {
        $collapsible_class = $this->options['collapsible'] ? 'toc-collapsible' : '';
        $position_class = 'toc-position-' . $this->options['position'];
        
        $html = '<div class="smart-toc ' . $collapsible_class . ' ' . $position_class . '">';
        $html .= '<div class="toc-header">';
        $html .= '<h3 class="toc-title">📋 目录导航</h3>';
        
        if ($this->options['collapsible']) {
            $html .= '<button class="toc-toggle" aria-label="折叠目录" title="折叠/展开">';
            $html .= '<span class="toggle-icon">-</span>';
            $html .= '</button>';
        }
        
        $html .= '</div>';
        $html .= '<div class="toc-content">';
        $html .= '<ul class="toc-list">';
        
        foreach ($headings as $heading) {
            $html .= '<li class="toc-item-level-' . $heading['level'] . '">';
            $html .= '<a href="#' . esc_attr($heading['anchor']) . '" class="toc-link">';
            $html .= '<span class="toc-bullet"></span>';
            $html .= '<span class="toc-text">' . esc_html($heading['text']) . '</span>';
            $html .= '</a></li>';
        }
        
        $html .= '</ul></div></div>';
        
        return $html;
    }
    
    /**
     * 插入目录到内容
     */
    private function insert_toc($content, $toc_html) {
        // 尝试在第一个标题前插入
        $pattern = '/<h[2-' . $this->options['depth'] . '][^>]*>/i';
        
        if (preg_match($pattern, $content, $matches, PREG_OFFSET_CAPTURE)) {
            $pos = $matches[0][1];
            $content = substr_replace($content, $toc_html, $pos, 0);
        } else {
            // 在文章开头插入
            $content = $toc_html . $content;
        }
        
        return $content;
    }
    
    /**
     * 短代码实现
     */
    public function toc_shortcode($atts) {
        global $post;
        
        $atts = shortcode_atts(array(
            'depth' => $this->options['depth'],
            'title' => '目录导航',
        ), $atts);
        
        $content = $post->post_content;
        $headings = $this->extract_headings($content);
        
        if (empty($headings)) {
            return '<div class="toc-notice">暂无目录可用</div>';
        }
        
        $html = '<div class="toc-shortcode">';
        $html .= '<h4 class="toc-shortcode-title">' . esc_html($atts['title']) . '</h4>';
        $html .= '<ul class="toc-shortcode-list">';
        
        foreach ($headings as $heading) {
            $html .= '<li class="toc-shortcode-item toc-level-' . $heading['level'] . '">';
            $html .= '<a href="#' . esc_attr($heading['anchor']) . '">';
            $html .= esc_html($heading['text']);
            $html .= '</a></li>';
        }
        
        $html .= '</ul></div>';
        
        return $html;
    }
    
    /**
     * 加载资源
     */
    public function enqueue_assets() {
        if (!is_single() && !is_page()) {
            return;
        }
        
        wp_enqueue_style('toc-styles', get_template_directory_uri() . '/css/toc.css', array(), '1.0.0');
        
        if ($this->options['smooth_scroll'] || $this->options['highlight_active']) {
            wp_enqueue_script('toc-script', get_template_directory_uri() . '/js/toc.js', array('jquery'), '1.0.0', true);
            
            wp_localize_script('toc-script', 'toc_config', array(
                'smoothScroll' => $this->options['smooth_scroll'],
                'highlightActive' => $this->options['highlight_active'],
                'offset' => 100,
            ));
        }
    }
    
    /**
     * 添加管理菜单
     */
    public function add_admin_menu() {
        add_options_page(
            '目录设置',
            '文章目录',
            'manage_options',
            'toc-settings',
            array($this, 'settings_page')
        );
    }
    
    /**
     * 注册设置
     */
    public function register_settings() {
        register_setting('toc_settings_group', 'toc_settings');
        
        add_settings_section('toc_main_section', '目录设置', null, 'toc_settings');
        
        add_settings_field('auto_insert', '自动插入目录', array($this, 'auto_insert_field'), 'toc_settings', 'toc_main_section');
        add_settings_field('min_headings', '最少标题数', array($this, 'min_headings_field'), 'toc_settings', 'toc_main_section');
        add_settings_field('depth', '标题深度', array($this, 'depth_field'), 'toc_settings', 'toc_main_section');
        add_settings_field('collapsible', '可折叠', array($this, 'collapsible_field'), 'toc_settings', 'toc_main_section');
    }
    
    /**
     * 设置页面
     */
    public function settings_page() {
        ?>
        <div class="wrap">
            <h1>文章目录设置</h1>
            <form method="post" action="options.php">
                <?php
                settings_fields('toc_settings_group');
                do_settings_sections('toc_settings');
                submit_button();
                ?>
            </form>
        </div>
        <?php
    }
    
    // 设置字段回调函数
    public function auto_insert_field() {
        $checked = isset($this->options['auto_insert']) ? $this->options['auto_insert'] : true;
        echo '<input type="checkbox" name="toc_settings[auto_insert]" value="1" ' . checked(1, $checked, false) . '>';
        echo '<p class="description">自动在文章开头插入目录(需要至少3个标题)</p>';
    }
    
    public function min_headings_field() {
        $value = isset($this->options['min_headings']) ? $this->options['min_headings'] : 3;
        echo '<input type="number" name="toc_settings[min_headings]" value="' . esc_attr($value) . '" min="2" max="10">';
    }
    
    public function depth_field() {
        $value = isset($this->options['depth']) ? $this->options['depth'] : 4;
        echo '<input type="number" name="toc_settings[depth]" value="' . esc_attr($value) . '" min="2" max="6">';
        echo '<p class="description">目录包含的标题层级(H2-H6)</p>';
    }
    
    public function collapsible_field() {
        $checked = isset($this->options['collapsible']) ? $this->options['collapsible'] : true;
        echo '<input type="checkbox" name="toc_settings[collapsible]" value="1" ' . checked(1, $checked, false) . '>';
        echo '<p class="description">目录是否可以折叠/展开</p>';
    }
}

// 初始化
TableOfContents::get_instance();

九、文章编辑器的自定义字段

/**
 * 添加目录控制元框
 */
function add_toc_metabox() {
    add_meta_box(
        'toc_settings_metabox',
        '目录设置',
        'render_toc_metabox',
        array('post', 'page'),
        'side',
        'default'
    );
}
add_action('add_meta_boxes', 'add_toc_metabox');

function render_toc_metabox($post) {
    wp_nonce_field('toc_metabox_nonce', 'toc_metabox_nonce_field');
    
    $show_toc = get_post_meta($post->ID, '_show_table_of_contents', true);
    $toc_position = get_post_meta($post->ID, '_toc_position', true);
    
    if ($show_toc === '') {
        $show_toc = 'auto';
    }
    
    ?>
    <div class="toc-metabox">
        <p>
            <label for="show_toc">显示目录:</label>
            <select name="show_toc" id="show_toc" style="width:100%;">
                <option value="auto" <?php selected($show_toc, 'auto'); ?>>自动(根据标题数量)</option>
                <option value="yes" <?php selected($show_toc, 'yes'); ?>>总是显示</option>
                <option value="no" <?php selected($show_toc, 'no'); ?>>不显示</option>
            </select>
        </p>
        
        <p>
            <label for="toc_position">目录位置:</label>
            <select name="toc_position" id="toc_position" style="width:100%;">
                <option value="top" <?php selected($toc_position, 'top'); ?>>文章开头</option>
                <option value="float-left" <?php selected($toc_position, 'float-left'); ?>>左侧浮动</option>
                <option value="float-right" <?php selected($toc_position, 'float-right'); ?>>右侧浮动</option>
            </select>
        </p>
        
        <p class="description">
            目录会自动从文章标题(H2-H4)生成,需要至少3个标题才会显示。
        </p>
    </div>
    
    <style>
    .toc-metabox p {
        margin: 1em 0;
    }
    .toc-metabox label {
        display: block;
        margin-bottom: 5px;
        font-weight: 600;
    }
    .toc-metabox .description {
        font-size: 12px;
        color: #666;
        font-style: italic;
    }
    </style>
    <?php
}

function save_toc_metabox($post_id) {
    if (!isset($_POST['toc_metabox_nonce_field']) || 
        !wp_verify_nonce($_POST['toc_metabox_nonce_field'], 'toc_metabox_nonce')) {
        return;
    }
    
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
        return;
    }
    
    if (!current_user_can('edit_post', $post_id)) {
        return;
    }
    
    if (isset($_POST['show_toc'])) {
        update_post_meta($post_id, '_show_table_of_contents', sanitize_text_field($_POST['show_toc']));
    }
    
    if (isset($_POST['toc_position'])) {
        update_post_meta($post_id, '_toc_position', sanitize_text_field($_POST['toc_position']));
    }
}
add_action('save_post', 'save_toc_metabox');

十、使用方法总结

1. 基础使用

将以下代码添加到主题的 functions.php文件中:

// 自动生成目录
add_filter('the_content', 'auto_generate_toc');

// 如果需要短代码支持
add_shortcode('toc', 'toc_shortcode');

2. 自定义配置

在文章编辑器中,通过短代码控制:


3. 文章级控制

每篇文章可以通过自定义字段单独控制是否显示目录及其位置。

4. 全局设置

在 WordPress 后台”设置” → “文章目录”中进行全局配置。

十一、SEO 优化建议

  1. 结构化数据:为目录添加合适的 Schema.org 标记
  2. 规范链接:确保锚点链接正确
  3. 标题层级:保持正确的标题层级(H1-H6)
  4. 移动端友好:确保目录在移动设备上可用
  5. 加载性能:避免目录影响页面加载速度

十二、最佳实践

  1. 最少标题:只有3个以上标题时才显示目录
  2. 响应式设计:确保在不同设备上正常显示
  3. 渐进增强:确保没有JavaScript时目录仍可用
  4. 性能优化:使用节流函数处理滚动事件
  5. 可访问性:确保键盘导航和屏幕阅读器支持
  6. 缓存策略:对生成的目录进行缓存

十三、常见问题解决

1. 目录不显示

  • 检查文章是否有足够的标题(H2-H4)
  • 检查是否有JavaScript错误
  • 检查CSS是否正确加载

2. 锚点链接无效

  • 确保标题有ID属性
  • 检查是否有重复的ID
  • 检查URL编码是否正确

3. 性能问题

  • 减少DOM操作
  • 使用事件委托
  • 实现虚拟滚动(如果目录很长)

4. 样式冲突

  • 使用特定的CSS类名前缀
  • 提高CSS特异性
  • 使用!important作为最后手段

十四、测试和验证

/**
 * 目录功能测试
 */
function test_toc_functionality() {
    if (current_user_can('manage_options')) {
        add_action('wp_footer', function() {
            echo '<div style="position:fixed;bottom:10px;right:10px;background:#fff;padding:10px;border:1px solid #000;z-index:9999;">';
            echo '<strong>目录测试</strong><br>';
            echo '标题数量: <span id="toc-test-count">0</span><br>';
            echo '目录可见: <span id="toc-test-visible">否</span><br>';
            echo '<button onclick="testTOC()">运行测试</button>';
            echo '</div>';
            
            echo '<script>
            function testTOC() {
                var headings = document.querySelectorAll("h2, h3, h4");
                document.getElementById("toc-test-count").textContent = headings.length;
                
                var toc = document.querySelector(".table-of-contents, .article-toc-wrapper");
                document.getElementById("toc-test-visible").textContent = toc ? "是" : "否";
                
                if (toc) {
                    var links = toc.querySelectorAll("a");
                    console.log("目录链接:", links.length);
                    
                    // 测试第一个链接
                    if (links.length > 0) {
                        var target = links[0].getAttribute("href");
                        if (target && document.querySelector(target)) {
                            console.log("锚点测试: 通过");
                        } else {
                            console.log("锚点测试: 失败");
                        }
                    }
                }
            }
            testTOC();
            </script>';
        });
    }
}
add_action('init', 'test_toc_functionality');

总结

通过本文介绍的方法,您可以在不依赖插件的情况下,为 WordPress 站点添加完整的内容目录功能。从简单的CSS实现到复杂的可配置系统,您可以根据自己的技术水平和需求选择合适的方法。

建议从基础版本开始,逐步添加高级功能。记住,良好的目录应该:

  1. 提升用户体验
  2. 不影响页面性能
  3. 支持响应式设计
  4. 提供无障碍访问
  5. 与网站设计风格一致

通过合理的实现,内容目录可以显著提高长篇文章的阅读体验,同时也有助于SEO优化。

这篇文章有用吗?

点击星号为它评分!

平均评分 0 / 5. 投票数: 0

到目前为止还没有投票!成为第一位评论此文章。

在AI里面继续讨论:

曾凤祥

曾凤祥

WordPress技术负责人
小兽WordPress凭借15年的WordPress企业网站开发经验,坚持以“为企业而生的WordPress服务”为宗旨,累计为10万多家客户提供高品质WordPress建站服务,得到了客户的一致好评。我们一直用心对待每一个客户,我们坚信:“善待客户,将会成为终身客户”。小兽WordPress能坚持多年,是因为我们一直诚信。

相关文章

如何让线上业务更上一层楼

还没有WordPress网站

还没有WordPress网站

不管你从事什么行业,WordPress都会为你提供一个专业的主题模板。在WordPress市场上有成千上万的免费主题,适合很多中小企业。

查看所有模板
已经有WordPress网站

已经有WordPress网站

小兽WordPress诚邀你一起学习WordPress,愿与各方携手升级改善您的WordPress网站,一起交流网站加速,网站优化等问题。

马上交个朋友
微信联系
chat 扫码联系
模板建站
挑选模板
网站定制
免费诊断
咨询热线
咨询热线

189-0733-7671

返回顶部