Typecho 免插件生成文章目录
迁移到 typecho 一段时间了,整体感觉虽然没有 WordPress 插件多,但够简洁易学习,能用简单的方式实现想要的功能,还是非常棒的。这不想实现一个文章目录的功能,大概搜索了下,也有相应的解决插件和代码,这里小编根据自己的需求,把网上的代码做了部分修改,功能函数独立放在 functions.php 中
/**
* 为文章标题添加锚点
* @param $content
* @return string|string[]|null
*/
function createTocIndex($content)
{
$tocCount = 0;
return preg_replace_callback('/<h([1-3])(.*?)>(.*?)<\/h\1>/i', function ($obj) use (&$tocCount) {
$tocCount++;
return '<h' . $obj[1] . $obj[2] . '><a name="index-' . $tocCount . '"></a>' . $obj[3] . '</h' . $obj[1] . '>';
}, $content);
}
/**
* 生成文章目录
* @param $content
*/
function createTocTree($content)
{
$tocArr = array();
$tocCount = 0;
preg_replace_callback('/<h([1-3])(.*?)>(.*?)<\/h\1>/i', function ($obj) use (&$tocArr, &$tocCount) {
$tocCount++;
$tocArr[] = array('text' => trim(strip_tags($obj[3])), 'depth' => $obj[1], 'count' => $tocCount);
}, $content);
$tocTree = '';
if ($tocArr) {
$tocTree = '';
$prevDepth = '';
$nextDepth = 0;
foreach ($tocArr as $tocItem) {
$tocDepth = $tocItem['depth'];
if ($prevDepth) {
if ($tocDepth == $prevDepth) {
$tocTree .= '</li>';
} elseif ($tocDepth > $prevDepth) {
$nextDepth++;
$tocTree .= '<ul class="widget-list">';
} else {
$nextDepth2 = ($nextDepth > ($prevDepth - $tocDepth)) ? ($prevDepth - $tocDepth) : $nextDepth;
if ($nextDepth2) {
for ($i = 0; $i < $nextDepth2; $i++) {
$tocTree .= '</li></ul>';
$nextDepth--;
}
}
$tocTree .= '</li>';
}
}
$tocTree .= '<li><a href="#index-' . $tocItem['count'] . '">' . $tocItem['text'] . '</a>';
$prevDepth = $tocItem['depth'];
}
for ($i = 0; $i <= $nextDepth; $i++) {
$tocTree .= '</li>';
}
}
return $tocTree;
}
使用方式跟网上的差不多,在需要生成目录的地方调用
<?php echo createTocTree($this->content); ?>
显示生成锚点内容的地方调用
<?php echo createTocIndex($this->content); ?>
大概就是这样了,显示效果可以参考本网站“文档”分类中的文章
参考:http://www.wusail.com/blog/index.php/27.html
更新:以上使用的是 php 实现的,下面使用 js 来实现的
梳理下大致流程,首先在 sidebar.php 增加文章目录存放的 div 代码
<?php if($this->type=='post'): ?>
<section class="widget sticky" id="tocTree">
<h3 class="widget-title"><?php _e('文章目录'); ?></h3>
<ul class="widget-list">
</ul>
</section>
<?php endif; ?>
然后使用 css 固定下,增加一个 .sticky 的样式
/*sticky*/
.sticky {
position: -webkit-sticky;
position: sticky;
top:0;
font-size: .92857em;
}
.sticky .widget-list{
overflow-y: auto;
max-height: 400px;
}
最后就是核心的 js 代码了
if (document.getElementById('secondary')) {
document.getElementById('secondary').style.height = document.getElementById('main').scrollHeight + 'px';
//生成文章目录
(function () {
let index = 0;
let depth = 0;
let tocTreeHtml = '';
let tocTreeObj = document.getElementById('tocTree')
let postContentObj = document.getElementById('main').querySelector('.post-content');
postContentObj.innerHTML = postContentObj.innerHTML.replace(/<h([1-6])(.*?)>(.*?)<\/h\1>/ig, function (match, num, attrs, html) {
index++;
if (depth < num) {
if (index > 1) {
tocTreeHtml += '</li><li><ul class="widget-list"><li><a href="#index-' + index + '">' + html + '</a>';
} else {
tocTreeHtml += '<li><a href="#index-' + index + '">' + html + '</a>';
}
} else if (depth == num) {
tocTreeHtml += '</li><li><a href="#index-' + index + '">' + html + '</a>';
} else if (depth > num) {
tocTreeHtml += '</li>' + (new Array(depth - num + 1).join('</ul></li>')) + '<li><a href="#index-' + index + '">' + html + '</a>';
}
depth = num;
return '<h' + num + attrs + ' id="index-' + (index) + '">' + html + '</h' + num + '>';
})
if (tocTreeHtml) {
tocTreeObj.style.display = 'block';
tocTreeObj.querySelector('.widget-list').innerHTML = tocTreeHtml;
}
})();
//文章目录跟随内容滚动
(function () {
let tocIndexNode = document.querySelectorAll('[id^="index"]');
let tocTreeListObj = document.getElementById('tocTree').querySelector('.widget-list');
if (tocIndexNode) {
window.addEventListener('scroll', function () {
let scrollTop = document.documentElement.scrollTop;
let windowHeight = window.innerHeight;
tocIndexNode.forEach(function (item, index, tocIndexNode) {
let tocNameObj = document.querySelector('[href="#' + item.attributes['id'].value + '"]');
if (tocIndexNode[Math.min(index + 1, tocIndexNode.length - 1)].offsetTop - scrollTop > 0 && item.offsetTop - scrollTop < windowHeight) {
tocNameObj.style.fontWeight = 'bolder';
tocTreeListObj.scrollTo(0, tocNameObj.offsetTop - tocTreeListObj.offsetHeight/2);
} else {
tocNameObj.style.fontWeight = 'normal';
}
})
})
}
})();
}
以上 js 是根据 typecho 默认主题写的,实现了文章目录,并自动跟随内容滚动
先试一下效果
试试先