在 WordPress 中为长篇文章添加内容目录(Table of Contents,TOC)可以显著改善用户体验和 SEO 表现。虽然有很多插件可以实现这个功能,但无插件方案可以提供更好的性能控制和代码简洁性。本文将详细介绍多种无插件实现内容目录的方法。
一、为什么需要无插件实现 TOC?
- 性能优化:减少插件依赖,提高页面加载速度
- 完全控制:自定义样式和功能
- 代码简洁:无需维护第三方插件更新
- 学习价值:深入理解 WordPress 开发原理
- 兼容性:避免与其他插件冲突
二、方法一:纯 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 优化建议
- 结构化数据:为目录添加合适的 Schema.org 标记
- 规范链接:确保锚点链接正确
- 标题层级:保持正确的标题层级(H1-H6)
- 移动端友好:确保目录在移动设备上可用
- 加载性能:避免目录影响页面加载速度
十二、最佳实践
- 最少标题:只有3个以上标题时才显示目录
- 响应式设计:确保在不同设备上正常显示
- 渐进增强:确保没有JavaScript时目录仍可用
- 性能优化:使用节流函数处理滚动事件
- 可访问性:确保键盘导航和屏幕阅读器支持
- 缓存策略:对生成的目录进行缓存
十三、常见问题解决
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实现到复杂的可配置系统,您可以根据自己的技术水平和需求选择合适的方法。
建议从基础版本开始,逐步添加高级功能。记住,良好的目录应该:
- 提升用户体验
- 不影响页面性能
- 支持响应式设计
- 提供无障碍访问
- 与网站设计风格一致
通过合理的实现,内容目录可以显著提高长篇文章的阅读体验,同时也有助于SEO优化。


湘公网安备43020002000238