Typecho插件实现添加文章目录的方法详解
发布:smiling 来源: PHP粉丝网 添加日期:2023-06-26 10:54:38 浏览: 评论:0
我的长博文不少,比较影响阅读体验,有必要添加一个文章目录功能。相比 Wordpress, Typecho 的插件就比较少了。我想找一个像掘金那样为文章添加目录的插件,没一个合适的。此类教程也不是很多,而且差不多都是前台 JavaScript 来实现的,感觉这样不如后台实现来的好。
注意:我使用的是Joe主题7.3,其他主题文件路径可能不一样。
添加文章标题锚点
1.声明 createAnchor 函数
在 core/functions.php 中添加如下代码:
- // 添加文章标题锚点
- function createAnchor($obj) {
- global $catalog;
- global $catalog_count;
- $catalog = array();
- $catalog_count = 0;
- $obj = preg_replace_callback('/<h([1-4])(.*?)>(.*?)<\/h\1>/i', function($obj) {
- global $catalog;
- global $catalog_count;
- $catalog_count ++;
- $catalog[] = array('text' => trim(strip_tags($obj[3])), 'depth' => $obj[1], 'count' => $catalog_count);
- return '<h'.$obj[1].$obj[2].' id="cl-'.$catalog_count.'">'.$obj[3].'</h'.$obj[1].'>';
- }, $obj);
- return $obj;
- }
也可以在标题元素内添加 <a> 标签,然后该标签新增 id 属性。
createAnchor 函数主要是通过正则表达式替换文章标题H1~H4来添加锚点,接下来我们需要调用它。
2.调用函数
同样在 core/core.php 中的 themeInit 方法最后一行之前添加如下代码:
- if ($self->is('single')) {
- $self->content = createAnchor($self->content);
- }
现在可以查看一下文章详情页面的源代码。文章的 H1~H4 元素应该添加了诸如 cl-1、cl-2 之类的 id 属性值。具体啥名不是关键,好记就行。
显示文章目录
1.声明 getCatalog 函数
在 core/functions.php 中添加如下代码:
- // 显示文章目录
- function getCatalog() {
- global $catalog;
- $str = '';
- if ($catalog) {
- $str = '<ul class="list">'."\n";
- $prev_depth = '';
- $to_depth = 0;
- foreach($catalog as $catalog_item) {
- $catalog_depth = $catalog_item['depth'];
- if ($prev_depth) {
- if ($catalog_depth == $prev_depth) {
- $str .= '</li>'."\n";
- } elseif ($catalog_depth > $prev_depth) {
- $to_depth++;
- $str .= '<ul class="sub-list">'."\n";
- } else {
- $to_depth2 = ($to_depth > ($prev_depth - $catalog_depth)) ? ($prev_depth - $catalog_depth) : $to_depth;
- if ($to_depth2) {
- for ($i=0; $i<$to_depth2; $i++) {
- $str .= '</li>'."\n".'</ul>'."\n";
- $to_depth--;
- }
- }
- $str .= '</li>';
- }
- }
- $str .= '<li class="item"><a class="link" href="#cl-'.$catalog_item['count'].'" rel="external nofollow" title="'.$catalog_item['text'].'">'.$catalog_item['text'].'</a>';
- $prev_depth = $catalog_item['depth'];
- }
- for ($i=0; $i<=$to_depth; $i++) {
- $str .= '</li>'."\n".'</ul>'."\n";
- }
- $str = '<section class="toc">'."\n".'<div class="title">文章目录</div>'."\n".$str.'</section>'."\n";
- }
- echo $str;
- }
getCatalog 方法通过递归 $catalog 数组生成文章目录,接下来我们需要调用它。
2.函数
最好将放在右侧边栏中。为此在 public/aside.php 中添加如下代码:
<?php if ($this->is('post')) getCatalog(); ?>
注意:只有文章才使用目录,独立页面那些不需要,所以加了判断。Typecho 有一些神奇的 is 语法可以方便二次开发,可以访问它的官网文档了解更多。
现在点击右侧的文章目录,可以滚动到相应的文章小标题位置了。
添加文章目录样式
可以看到,当前的文章目录还比较丑陋,我们来美化一下。在 assets/css/joe.post.min.scss 中添加如下 SCSS 代码:
- .joe_aside {
- .toc {
- position: sticky;
- top: 20px;
- width: 250px;
- background: var(--background);
- border-radius: var(--radius-wrap);
- box-shadow: var(--box-shadow);
- overflow: hidden;
- .title {
- display: block;
- border-bottom: 1px solid var(--classA);
- font-size: 16px;
- font-weight: 500;
- height: 45px;
- line-height: 45px;
- text-align: center;
- color: var(--theme);
- }
- .list {
- padding-top: 10px;
- padding-bottom: 10px;
- max-height: calc(100vh - 80px);
- overflow: auto;
- .link {
- display: block;
- padding: 8px 16px;
- border-left: 4px solid transparent;
- color: var(--main);
- text-decoration: none;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- &:hover {
- background-color: var(--classC);
- }
- &.active {
- border-left-color: var(--theme);
- }
- }
- }
- }
- }
为了方便操作,将 .toc 设置成 position: sticky; 实现了吸顶定位。考虑到文章目录可能很多,为 .toc 列表添加了 overflow: auto;,如代码第 3 ~ 4 行。
由于 .joe_header(主题标头)也使用了吸顶定位,导致和文章目录有遮挡,所有加了 has_toc .joe_header 来取消页面主题标头的吸顶功能,如下代码:
- .has_toc {
- .joe_header {
- position: relative;
- }
- }
定位到文章
要显示文章目录当前选中项的状态,需要用到 JavaScript 给选中项添加一个 active 样式。在 assets/js/joe.post_page.js 中添加如下代码:
- var headings = $('.joe_detail__article').find('h1, h2, h3, h4');
- var links = $('.toc .link');
- var tocList = document.querySelector('.tocr > .list');
- var itemHeight = $('.toc .item').height();
- var distance = tocList.scrollHeight - tocList.clientHeight;
- var timer = 0;
- // 是否自动滚动
- var autoScrolling = true;
- function setItemActive(id) {
- links.removeClass('active');
- var link = links.filter("[href='#" + id + "']")
- link.addClass('active');
- }
- function onChange() {
- autoScrolling = true;
- if (location.hash) {
- id = location.hash.substr(1);
- var heading = headings.filter("[id='" + id + "']");
- var top = heading.offset().top - 15;
- window.scrollTo({ top: top })
- setItemActive(id)
- }
- }
- window.addEventListener('hashchange', onChange);
- // hash没有改变时手动调用一次
- onChange();
由于布局和滚动动画的影响,导致锚点定位有点偏差。我们再 setItemActive 函数中用 scrollTo 或 scrollIntoView 来纠正。另外,我们希望有锚点的链接可以直接定位,因此监听了 hashchange 事件。点击文章目录测试一下定位,再手动键入锚点测试一下,应该都没啥问题。
定位到目录
目前可以从文章目录定位到文章标题了,是单向定位,双向定位还需要实现滚动文章内容时定位到文章目录的当前项。正如我们马上能想到的,需要监听 window 的 scroll 事件,如下代码:
- function onScroll() {
- if (timer) {
- clearTimeout(timer);
- }
- timer = setTimeout(function () {
- var top = $(window).scrollTop();
- var count = headings.length;
- for (var i = 0; i < count; i++) {
- var j = i;
- // 滚动和点击时 index 相差 1,需要 autoScrolling 来区分
- if (i > 0 && !autoScrolling) {
- j = i - 1;
- }
- var headingTop = $(headings[i]).offset().top;
- var listTop = distance * i / count
- // 判断滚动条滚动距离是否大于当前滚动项可滚动距离
- if (headingTop > top) {
- var id = $(headings[j]).attr('id');
- setItemActive(id);
- // 如果目录列表有滑条,使被选中的下一元素可见
- if (listTop > 0) {
- // 向上滚动
- if (listTop < itemHeight) {
- listTop -= itemHeight;
- } else {
- listTop += itemHeight;
- }
- $(tocList).scrollTop(listTop)
- }
- break;
- } else if (i === count - 1) {
- // 特殊处理最后一个元素
- var id = $(headings[i]).attr('id');
- setItemActive(id);
- if (listTop > 0) {
- $(tocList).scrollTop(distance)
- }
- }
- }
- autoScrolling = false;
- }, 100);
- }
- $(window).on('scroll', onScroll);
首先,在 onScroll 事件处理函数中遍历标题数组 headings, 如果滚动条滚动距离 top 大于当前标题项 item 可滚动距离 headingTop,再调用 setItemActive 函数,传入当前的标题项的 id 来判断文章目录激活状态。
如果目录列表有滑条,调用 jQuery 的 scrollTop 方法滚动目录列表滑条,使被选中目录项的上下元素可见,现在文章目录基本上可用了,也还美观,后续可以考虑优化再封装成一个插件。
吐槽一下:Joe 主题太依赖jQuery了,修改起来费劲 ::(汗)。
Tags: Typecho插件 Typecho添加文章目录
- 上一篇:Laravel操作redis和缓存操作详解
- 下一篇:最后一页
相关文章
- ·typecho插件编写教程(一):Hello World(2021-05-27)
- ·typecho插件编写教程(二):写一个新插件(2021-05-27)
- ·typecho插件编写教程(三):保存配置(2021-05-27)
- ·typecho插件编写教程(四):插件挂载(2021-05-27)
- ·typecho插件编写教程(五):核心代码(2021-05-27)
- ·typecho插件编写教程(六):调用接口(2021-05-27)
推荐文章
热门文章
最新评论文章
- 写给考虑创业的年轻程序员(10)
- PHP新手上路(一)(7)
- 惹恼程序员的十件事(5)
- PHP邮件发送例子,已测试成功(5)
- 致初学者:PHP比ASP优秀的七个理由(4)
- PHP会被淘汰吗?(4)
- PHP新手上路(四)(4)
- 如何去学习PHP?(2)
- 简单入门级php分页代码(2)
- php中邮箱email 电话等格式的验证(2)