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 默认主题写的,实现了文章目录,并自动跟随内容滚动

标签: 无

已有 2 条评论

  1. 先试一下效果

    1. A.wei A.wei

      试试先

评论已关闭