翼度科技»论坛 编程开发 PHP 查看内容

Typecho插件实现添加文章目录的方法详解

6

主题

6

帖子

18

积分

新手上路

Rank: 1

积分
18
我的长博文不少,比较影响阅读体验,有必要添加一个文章目录功能。相比 Wordpress, Typecho 的插件就比较少了。我想找一个像掘金那样为文章添加目录的插件,没一个合适的。此类教程也不是很多,而且差不多都是前台 JavaScript 来实现的,感觉这样不如后台实现来的好。
注意:我使用的是Joe主题7.3,其他主题文件路径可能不一样。

添加文章标题锚点

1.声明 createAnchor 函数
  1. core/functions.php
复制代码
中添加如下代码:
  1. // 添加文章标题锚点
  2. function createAnchor($obj) {
  3.   global $catalog;
  4.   global $catalog_count;
  5.   $catalog = array();
  6.   $catalog_count = 0;
  7.   $obj = preg_replace_callback('/<h([1-4])(.*?)>(.*?)<\/h\1>/i', function($obj) {
  8.     global $catalog;
  9.     global $catalog_count;
  10.     $catalog_count ++;
  11.     $catalog[] = array('text' => trim(strip_tags($obj[3])), 'depth' => $obj[1], 'count' => $catalog_count);
  12.     return '<h'.$obj[1].$obj[2].' id="cl-'.$catalog_count.'">'.$obj[3].'</h'.$obj[1].'>';
  13.   }, $obj);
  14.   return $obj;
  15. }
复制代码
也可以在标题元素内添加
  1. <a>
复制代码
标签,然后该标签新增
  1. id
复制代码
属性。
  1. createAnchor
复制代码
函数主要是通过正则表达式替换文章标题H1~H4来添加锚点,接下来我们需要调用它。
2.调用函数
同样在
  1. core/core.php
复制代码
中的
  1. themeInit
复制代码
方法最后一行之前添加如下代码:
  1. if ($self->is('single')) {
  2.   $self->content = createAnchor($self->content);
  3. }
复制代码
现在可以查看一下文章详情页面的源代码。文章的
  1. H1~H4
复制代码
元素应该添加了诸如
  1. cl-1
复制代码
  1. cl-2
复制代码
之类的
  1. id
复制代码
属性值。具体啥名不是关键,好记就行。

显示文章目录

1.声明 getCatalog 函数
  1. core/functions.php
复制代码
中添加如下代码:
  1. // 显示文章目录
  2. function getCatalog() {  
  3.   global $catalog;
  4.   $str = '';
  5.   if ($catalog) {
  6.     $str = '<ul class="list">'."\n";
  7.     $prev_depth = '';
  8.     $to_depth = 0;
  9.     foreach($catalog as $catalog_item) {
  10.       $catalog_depth = $catalog_item['depth'];
  11.       if ($prev_depth) {
  12.         if ($catalog_depth == $prev_depth) {
  13.           $str .= '</li>'."\n";
  14.         } elseif ($catalog_depth > $prev_depth) {
  15.           $to_depth++;
  16.           $str .= '<ul class="sub-list">'."\n";
  17.         } else {
  18.           $to_depth2 = ($to_depth > ($prev_depth - $catalog_depth)) ? ($prev_depth - $catalog_depth) : $to_depth;
  19.           if ($to_depth2) {
  20.             for ($i=0; $i<$to_depth2; $i++) {
  21.               $str .= '</li>'."\n".'</ul>'."\n";
  22.               $to_depth--;
  23.             }
  24.           }
  25.           $str .= '</li>';
  26.         }
  27.       }
  28.       $str .= '<li class="item"><a class="link" href="#cl-'.$catalog_item['count'].'" rel="external nofollow"  title="'.$catalog_item['text'].'">'.$catalog_item['text'].'</a>';
  29.       $prev_depth = $catalog_item['depth'];
  30.     }
  31.     for ($i=0; $i<=$to_depth; $i++) {
  32.       $str .= '</li>'."\n".'</ul>'."\n";
  33.     }
  34.     $str = '<section class="toc">'."\n".'<div class="title">文章目录</div>'."\n".$str.'</section>'."\n";
  35.   }
  36.   echo $str;
  37. }
复制代码
  1. getCatalog
复制代码
方法通过递归
  1. $catalog
复制代码
数组生成文章目录,接下来我们需要调用它。
2.函数
最好将放在右侧边栏中。为此在
  1. public/aside.php
复制代码
中添加如下代码:
  1. <?php if ($this->is('post')) getCatalog(); ?>
复制代码
注意:只有文章才使用目录,独立页面那些不需要,所以加了判断。Typecho 有一些神奇的
  1. is
复制代码
语法可以方便二次开发,可以访问它的官网文档了解更多。
现在点击右侧的文章目录,可以滚动到相应的文章小标题位置了。

添加文章目录样式

可以看到,当前的文章目录还比较丑陋,我们来美化一下。在
  1. assets/css/joe.post.min.scss
复制代码
中添加如下 SCSS 代码:
  1. .joe_aside {
  2.   .toc {
  3.     position: sticky;
  4.     top: 20px;
  5.     width: 250px;
  6.     background: var(--background);
  7.     border-radius: var(--radius-wrap);
  8.     box-shadow: var(--box-shadow);
  9.     overflow: hidden;

  10.     .title {
  11.       display: block;
  12.       border-bottom: 1px solid var(--classA);
  13.       font-size: 16px;
  14.       font-weight: 500;
  15.       height: 45px;
  16.       line-height: 45px;
  17.       text-align: center;
  18.       color: var(--theme);
  19.     }

  20.     .list {
  21.       padding-top: 10px;
  22.       padding-bottom: 10px;
  23.       max-height: calc(100vh - 80px);
  24.       overflow: auto;

  25.       .link {
  26.         display: block;
  27.         padding: 8px 16px;
  28.         border-left: 4px solid transparent;
  29.         color: var(--main);
  30.         text-decoration: none;
  31.         white-space: nowrap;
  32.         overflow: hidden;
  33.         text-overflow: ellipsis;

  34.         &:hover {
  35.           background-color: var(--classC);
  36.         }

  37.         &.active {
  38.           border-left-color: var(--theme);
  39.         }
  40.       }
  41.     }
  42.   }
  43. }
复制代码
为了方便操作,将
  1. .toc
复制代码
设置成
  1. position: sticky;
复制代码
实现了吸顶定位。考虑到文章目录可能很多,为
  1. .toc
复制代码
列表添加了
  1. overflow: auto;
复制代码
,如代码第
  1. 3 ~ 4
复制代码
行。
由于
  1. .joe_header
复制代码
(主题标头)也使用了吸顶定位,导致和文章目录有遮挡,所有加了
  1. has_toc .joe_header
复制代码
来取消页面主题标头的吸顶功能,如下代码:
  1. .has_toc {
  2.   .joe_header {
  3.     position: relative;
  4.   }
  5. }
复制代码
定位到文章

要显示文章目录当前选中项的状态,需要用到 JavaScript 给选中项添加一个
  1. active
复制代码
样式。在
  1. assets/js/joe.post_page.js
复制代码
中添加如下代码:
  1. var headings = $('.joe_detail__article').find('h1, h2, h3, h4');
  2. var links = $('.toc .link');
  3. var tocList = document.querySelector('.tocr > .list');
  4. var itemHeight = $('.toc .item').height();
  5. var distance = tocList.scrollHeight - tocList.clientHeight;
  6. var timer = 0;
  7. // 是否自动滚动
  8. var autoScrolling = true;

  9. function setItemActive(id) {
  10.   links.removeClass('active');
  11.   var link = links.filter("[href='#" + id + "']")
  12.   link.addClass('active');
  13. }

  14. function onChange() {
  15.   autoScrolling = true;
  16.   if (location.hash) {
  17.     id = location.hash.substr(1);
  18.     var heading = headings.filter("[id='" + id + "']");
  19.     var top = heading.offset().top - 15;
  20.     window.scrollTo({ top: top })
  21.     setItemActive(id)
  22.   }
  23. }
  24. window.addEventListener('hashchange', onChange);
  25. // hash没有改变时手动调用一次
  26. onChange();
复制代码
由于布局和滚动动画的影响,导致锚点定位有点偏差。我们再
  1. setItemActive
复制代码
函数中用
  1. scrollTo
复制代码
  1. scrollIntoView
复制代码
来纠正。另外,我们希望有锚点的链接可以直接定位,因此监听了
  1. hashchange
复制代码
事件。点击文章目录测试一下定位,再手动键入锚点测试一下,应该都没啥问题。

定位到目录

目前可以从文章目录定位到文章标题了,是单向定位,双向定位还需要实现滚动文章内容时定位到文章目录的当前项。正如我们马上能想到的,需要监听
  1. window
复制代码
  1. scroll
复制代码
事件,如下代码:
  1. function onScroll() {
  2.   if (timer) {
  3.     clearTimeout(timer);
  4.   }
  5.   timer = setTimeout(function () {
  6.     var top = $(window).scrollTop();
  7.     var count = headings.length;
  8.     for (var i = 0; i < count; i++) {
  9.       var j = i;
  10.       // 滚动和点击时 index 相差 1,需要 autoScrolling 来区分
  11.       if (i > 0 && !autoScrolling) {
  12.         j = i - 1;
  13.       }
  14.       var headingTop = $(headings[i]).offset().top;
  15.       var listTop = distance * i / count
  16.       // 判断滚动条滚动距离是否大于当前滚动项可滚动距离
  17.       if (headingTop > top) {
  18.         var id = $(headings[j]).attr('id');
  19.         setItemActive(id);
  20.         // 如果目录列表有滑条,使被选中的下一元素可见
  21.         if (listTop > 0) {
  22.           // 向上滚动
  23.           if (listTop < itemHeight) {
  24.             listTop -= itemHeight;
  25.           } else {
  26.             listTop += itemHeight;
  27.           }
  28.           $(tocList).scrollTop(listTop)
  29.         }
  30.         break;
  31.       } else if (i === count - 1) {
  32.         // 特殊处理最后一个元素
  33.         var id = $(headings[i]).attr('id');
  34.         setItemActive(id);
  35.         if (listTop > 0) {
  36.           $(tocList).scrollTop(distance)
  37.         }
  38.       }
  39.     }
  40.     autoScrolling = false;
  41.   }, 100);
  42. }

  43. $(window).on('scroll', onScroll);
复制代码
首先,在
  1. onScroll
复制代码
事件处理函数中遍历标题数组
  1. headings
复制代码
, 如果滚动条滚动距离
  1. top
复制代码
大于当前标题项
  1. item
复制代码
可滚动距离
  1. headingTop
复制代码
,再调用
  1. setItemActive
复制代码
函数,传入当前的标题项的
  1. id
复制代码
来判断文章目录激活状态。
如果目录列表有滑条,调用 jQuery 的
  1. scrollTop
复制代码
方法滚动目录列表滑条,使被选中目录项的上下元素可见,
现在文章目录基本上可用了,也还美观,后续可以考虑优化再封装成一个插件。
吐槽一下:Joe 主题太依赖jQuery了,修改起来费劲 :汗)。
到此这篇关于Typecho插件实现添加文章目录的方法详解的文章就介绍到这了,更多相关Typecho添加文章目录内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

来源:https://www.jb51.net/article/275778.htm
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具